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.
Endpoints
Section titled “Endpoints”| Method | Path | Auth | Purpose |
|---|---|---|---|
GET | /api/v1/events/{slug}/floor-plans | No | List published floor plans for the event |
GET | /api/v1/events/{slug}/floor-plans/{id} | No | Single floor plan with full marker list |
Both 404 for unpublished events or unpublished plans.
List published floor plans
Section titled “List published floor plans”GET /api/v1/events/annual-tech-summit-2026/floor-plansLightweight 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" } ]}| Field | Notes |
|---|---|
image_url | Absolute URL to the floor plan image (PNG, JPG, SVG, or WebP) |
width, height | Reference dimensions in pixels — used for scaling markers |
sort_order | Organizer-controlled display order; smaller values come first. Use this when rendering a “Choose floor plan” picker. |
coordinate_system | Always "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_count | Total interactive markers attached |
updated_at | Bumps on plan edits AND marker edits (the marker model touches its parent). Safe to use as a single cache-invalidation key. |
Get a single floor plan
Section titled “Get a single floor plan”GET /api/v1/events/annual-tech-summit-2026/floor-plans/1Returns 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.
Marker fields
Section titled “Marker fields”| Field | Type | Notes |
|---|---|---|
id | integer | Stable across edits; safe to cache locally |
type | enum | booth · room · facility · entrance · exit · info (forward-compatible — see type extensibility) |
label | string | Always shown when the marker is highlighted |
description | string | null | Optional; shown in a detail pane / bottom sheet on tap |
x, y | float | Percentage of image dimensions (0–100). See positioning |
link_type | enum | null | booth · session · partner · url — drives navigation on tap |
link_id | integer | null | ID of the linked resource (paired with link_type) |
link_url | string | null | External URL when link_type === 'url' |
color | string | Hex color (#rrggbb) — organizer’s preferred pin tint |
icon | string | Icon hint (pin, store, screen, door, bath, alert, etc.) |
sort_order | integer | Markers come back already sorted; reuse for stable client-side rendering |
Positioning
Section titled “Positioning”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.
Tap navigation
Section titled “Tap navigation”When a marker is tapped:
link_type | What to do |
|---|---|
booth | Show booth details inline, or navigate to a booth-detail screen |
session | Navigate to the session in the agenda using link_id |
partner | Navigate to the partner profile using link_id (e.g. GET /api/v1/events/{slug}/partners) |
url | Open link_url in an external browser |
null | Show only label + description in a popup, no navigation |
Type extensibility
Section titled “Type extensibility”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.
Caching guidance
Section titled “Caching guidance”Both endpoints emit a Last-Modified HTTP header and honor
If-Modified-Since for conditional GETs. Recommended client pattern is
stale-while-revalidate:
- Cache the response on disk indefinitely (or until app upgrade).
- On every screen open, fetch with the cached
Last-Modifiedvalue asIf-Modified-Since. - A
304 Not Modifiedresponse (~150 bytes) means “your cache is good.” - A
200 OKresponse 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.