Skip to content

Floor Plans

Lets the mobile app render the venue floor plan with tap-to-navigate markers (booths, rooms, entrances, facilities, etc.). Two endpoints — both public (no authentication needed) — matching the agenda/speakers pattern.

MethodPathAuthPurpose
GET/api/v1/events/{slug}/floor-plansNoList published floor plans for the event
GET/api/v1/events/{slug}/floor-plans/{id}NoSingle floor plan with full marker list

Both 404 for unpublished events or unpublished plans.


GET /api/v1/events/annual-tech-summit-2026/floor-plans

Lightweight payload — perfect when the venue has multiple plans (e.g. ground floor + upper floor) and you want a “Choose floor plan” screen first. No markers in this response; fetch them via the show endpoint.

{
"success": true,
"message": "Success",
"data": [
{
"id": 1,
"name": "Ground floor",
"image_url": "https://odavent.com/storage/4/ground-floor.svg",
"width": 1000,
"height": 700,
"sort_order": 0,
"coordinate_system": "percent",
"markers_count": 6,
"updated_at": "2026-04-10T18:05:14+00:00"
}
]
}
FieldNotes
image_urlAbsolute URL to the floor plan image (PNG, JPG, SVG, or WebP)
width, heightReference dimensions in pixels — used for scaling markers
sort_orderOrganizer-controlled display order; smaller values come first. Use this when rendering a “Choose floor plan” picker.
coordinate_systemAlways "percent" today. Marker x/y are 0–100 percentages of image dimensions. Reserved as a contract field — clients should read it rather than hard-code.
markers_countTotal interactive markers attached
updated_atBumps on plan edits AND marker edits (the marker model touches its parent). Safe to use as a single cache-invalidation key.

GET /api/v1/events/annual-tech-summit-2026/floor-plans/1

Returns the plan plus every marker, ordered by sort_order.

{
"success": true,
"message": "Success",
"data": {
"id": 1,
"name": "Ground floor",
"image_url": "https://odavent.com/storage/4/ground-floor.svg",
"width": 1000,
"height": 700,
"sort_order": 0,
"coordinate_system": "percent",
"markers": [
{
"id": 1,
"type": "entrance",
"label": "Main Entrance",
"description": "Hall A — registration desks open from 8:00",
"x": 50.0,
"y": 95.0,
"link_type": null,
"link_id": null,
"link_url": null,
"color": "#22c55e",
"icon": "door",
"sort_order": 1
},
{
"id": 2,
"type": "booth",
"label": "Booth A4 — Acme Inc",
"description": "Industrial automation solutions",
"x": 23.5,
"y": 67.2,
"link_type": "partner",
"link_id": 1,
"link_url": null,
"color": "#6366f1",
"icon": "store",
"sort_order": 2
},
{
"id": 4,
"type": "room",
"label": "Workshop Room 2",
"description": "Capacity 40 — Track: Workshops",
"x": 72.4,
"y": 40.1,
"link_type": "session",
"link_id": 5,
"link_url": null,
"color": "#f59e0b",
"icon": "screen",
"sort_order": 4
},
{
"id": 5,
"type": "info",
"label": "Live floor cam",
"description": "External camera feed",
"x": 50.0,
"y": 5.0,
"link_type": "url",
"link_id": null,
"link_url": "https://camera.odavent.com/hall-a",
"color": "#0ea5e9",
"icon": "camera",
"sort_order": 5
}
],
"updated_at": "2026-04-10T18:05:14+00:00"
}
}

link_url is only populated when link_type === "url"; ignore it for the other link types.


FieldTypeNotes
idintegerStable across edits; safe to cache locally
typeenumbooth · room · facility · entrance · exit · info (forward-compatible — see type extensibility)
labelstringAlways shown when the marker is highlighted
descriptionstring | nullOptional; shown in a detail pane / bottom sheet on tap
x, yfloatPercentage of image dimensions (0–100). See positioning
link_typeenum | nullbooth · session · partner · url — drives navigation on tap
link_idinteger | nullID of the linked resource (paired with link_type)
link_urlstring | nullExternal URL when link_type === 'url'
colorstringHex color (#rrggbb) — organizer’s preferred pin tint
iconstringIcon hint (pin, store, screen, door, bath, alert, etc.)
sort_orderintegerMarkers come back already sorted; reuse for stable client-side rendering

Markers are stored as percentages of the image’s natural width and height, not pixel coordinates. This keeps them correct under any scaling/zoom on the client.

To convert a marker’s (x, y) into pixel coordinates on a rendered image:

pixelX = renderedImageWidth * (marker.x / 100)
pixelY = renderedImageHeight * (marker.y / 100)

Origin is top-left (0,0); (100,100) is the bottom-right corner.

When a marker is tapped:

link_typeWhat to do
boothShow booth details inline, or navigate to a booth-detail screen
sessionNavigate to the session in the agenda using link_id
partnerNavigate to the partner profile using link_id (e.g. GET /api/v1/events/{slug}/partners)
urlOpen link_url in an external browser
nullShow only label + description in a popup, no navigation

The marker type enum may grow over time (planned candidates: stage, food, network_zone, info_desk, parking). New types will be added without an API version bump.

Clients MUST handle unknown types defensively — fall back to a neutral pin/popup using the label + description rather than crashing or skipping the marker. The color and icon fields stay accurate for any new type, so a renderer that respects them gracefully degrades.


Both endpoints emit a Last-Modified HTTP header and honor If-Modified-Since for conditional GETs. Recommended client pattern is stale-while-revalidate:

  1. Cache the response on disk indefinitely (or until app upgrade).
  2. On every screen open, fetch with the cached Last-Modified value as If-Modified-Since.
  3. A 304 Not Modified response (~150 bytes) means “your cache is good.”
  4. A 200 OK response replaces the cache.

Marker IDs are stable across organizer edits (upsert-by-ID semantics on the bulk-save flow), so deep links like ?marker=42 survive plan edits and client-side animations between updates are reliable.

markers_count is for UI gating (“hide the floor-plan tab when zero”), not for cache invalidation. Always use Last-Modified or updated_at as the invalidation key — count stays stable when a marker is moved or relabelled.

The image at image_url is served from a CDN with long-lived caching — let the platform’s HTTP cache handle it.