REST API
Base path /api/v1. Authenticate with Authorization: Bearer <key> or X-Api-Key; the organiser session cookie also works for same-origin calls. Responses are { data, meta? } or { error: { code, message, details? } }. Event ids accept the slug. Machine-readable spec: /api/v1/openapi.json.
KEY=tsr_live_demo_9f3b1c7e2a4d6f8b0c1d2e3f4a5b6c7d
# Bulk upsert booths from a CSV export of your CAD tool
curl -X POST -H "Authorization: Bearer $KEY" -F file=@booths.csv http://localhost:3000/api/v1/events/grip-connect-2026/booths/import
# Put a booth on hold for 2 hours
curl -X POST -H "Authorization: Bearer $KEY" -H "Content-Type: application/json" -d '{"status":"held","holdMinutes":120}' \
http://localhost:3000/api/v1/events/grip-connect-2026/booths/A101/status
# Route between two booths (public)
curl "http://localhost:3000/api/v1/events/grip-connect-2026/route?from=booth:A101&to=booth:T05&accessible=1"Auth
/api/v1/meWho am I
/api/v1/api-keysList API keys
/api/v1/api-keysCreate key (organiser session only; key returned once)
Body: { name, scopes? }
/api/v1/api-keys/{id}Revoke key
Events
/api/v1/eventsList events
/api/v1/eventsCreate event
Body: EventInput
/api/v1/events/{event}Get event
/api/v1/events/{event}Update event (settings deep-merge)
Body: Partial<EventInput>
/api/v1/events/{event}Delete event
/api/v1/events/{event}/publishPublish a new floor plan version
Body: { note? }
/api/v1/events/{event}/unpublishSet event back to draft
/api/v1/events/{event}/duplicateCopy the map into a new draft event
Body: { name, slug?, includeExhibitors? }
/api/v1/events/{event}/versionsPublished versions
/api/v1/events/{event}/bundleFull plan bundle (live, or ?published=1)
Query: published (1 for the last published snapshot)
/api/v1/events/{event}/export/{format}Export: expofp | geojson | booths.csv | exhibitors.csv | bundle | offline
Levels
/api/v1/events/{event}/levelsList levels
/api/v1/events/{event}/levelsCreate level
Body: LevelInput
/api/v1/events/{event}/levels/{id}Get level
/api/v1/events/{event}/levels/{id}Update level
Body: Partial<LevelInput>
/api/v1/events/{event}/levels/{id}Delete level
/api/v1/events/{event}/levels/reorderReorder levels
Body: { ids: string[] }
Booths
/api/v1/events/{event}/boothsList booths
Query: levelIdstatus (available|held|reserved|sold|unavailable)q (search label/externalId)exhibitorIdlimitoffset
/api/v1/events/{event}/boothsCreate booth (object) or bulk upsert (array, matched by externalId then label)
Body: BoothInput | BoothInput[]
/api/v1/events/{event}/booths/{id}Get booth by id, label or externalId
/api/v1/events/{event}/booths/{id}Update booth
Body: Partial<BoothInput>
/api/v1/events/{event}/booths/{id}Delete booth
/api/v1/events/{event}/booths/{id}/assignAssign an exhibitor
Body: { exhibitorId, markSold? }
/api/v1/events/{event}/booths/{id}/assignReplace exhibitors
Body: { exhibitorIds }
/api/v1/events/{event}/booths/{id}/assignUnassign an exhibitor
Body: { exhibitorId }
/api/v1/events/{event}/booths/{id}/statusSet status (held takes holdMinutes)
Body: { status: available|held|reserved|sold|unavailable, holdMinutes? }
/api/v1/events/{event}/booths/mergeMerge adjacent booths
Body: { boothIds, label? }
/api/v1/events/{event}/booths/importCSV import (label, level, x, y, width, height, type, status, price, external_id, notes)
Body: { csv } | multipart file | text/csv
Sales
/api/v1/events/{event}/booths/{id}/quotepublicPublic price quote
/api/v1/events/{event}/exhibitors/{id}/extrasExtras held
/api/v1/events/{event}/exhibitors/{id}/extrasAssign extra
Body: { extraId, quantity?, boothId? }
/api/v1/events/{event}/exhibitors/{id}/extrasRemove extra
Body: { extraId }
/api/v1/events/{event}/reservepublicPublic: reserve/buy a booth
Body: { boothId, company, contactName, contactEmail, exhibitorId?, notes?, extras? }
/api/v1/events/{event}/ordersList orders
Query: statusboothIdexhibitorId
/api/v1/events/{event}/orders/summarySales summary
/api/v1/events/{event}/orders/expireExpire timed-out holds now
/api/v1/events/{event}/orders/{id}Get order
/api/v1/events/{event}/orders/{id}Update notes/contact/amount
/api/v1/events/{event}/orders/{id}/payMark paid (booth → sold)
Body: { providerRef? }
/api/v1/events/{event}/orders/{id}/invoiceMark invoiced (booth → reserved)
/api/v1/events/{event}/orders/{id}/cancelCancel (booth → available)
Body: { reason? }
/api/v1/events/{event}/orders/{id}/refundRefund (booth → available)
/api/v1/events/{event}/orders/{id}/checkoutpublicPublic: redirect to Stripe Checkout
/api/v1/events/{event}/orders/{id}/mock-paypublicPublic (mock provider): simulate payment
/api/v1/events/{event}/pricing-rulesList pricing rules
/api/v1/events/{event}/pricing-rulesCreate rule
Body: { name, boothType?: standard|corner|peninsula|island|table|shell|sponsor|custom, minAreaM2?, maxAreaM2?, priceCents? | pricePerM2Cents?, currency? }
/api/v1/events/{event}/pricing-rules/{id}Update
/api/v1/events/{event}/pricing-rules/{id}Delete
/api/v1/events/{event}/extrasSponsorships & booth extras
/api/v1/events/{event}/extrasCreate extra
Body: { kind, name, description?, priceCents?, limitPerEvent?, limitPerExhibitor?, reserveOrBuyAllowed? }
/api/v1/events/{event}/extras/{id}Update
/api/v1/events/{event}/extras/{id}Delete
Exhibitors
/api/v1/events/{event}/exhibitorsList exhibitors
Query: qcategoryIdlevelIdfeatured (1|0)unassigned (1)limitoffset
/api/v1/events/{event}/exhibitorsCreate (object) or bulk upsert (array; externalId → gripId → name)
Body: ExhibitorInput | ExhibitorInput[]
/api/v1/events/{event}/exhibitors/{id}Get by id, slug or externalId
/api/v1/events/{event}/exhibitors/{id}Update exhibitor
Body: Partial<ExhibitorInput>
/api/v1/events/{event}/exhibitors/{id}Delete exhibitor
/api/v1/events/{event}/exhibitors/{id}/portal-linkSelf-service magic link
/api/v1/events/{event}/exhibitors/{id}/portal-linkRotate magic link
/api/v1/events/{event}/exhibitors/importCSV import (ExpoFP template compatible)
Body: { csv } | multipart file
Categories
/api/v1/events/{event}/categoriesList categories (with counts)
/api/v1/events/{event}/categoriesCreate category
Body: { name, color?, parentId?, sortIndex? }
/api/v1/events/{event}/categories/{id}Get category
/api/v1/events/{event}/categories/{id}Update
/api/v1/events/{event}/categories/{id}Delete
Sessions
/api/v1/events/{event}/sessionsList sessions
/api/v1/events/{event}/sessionsCreate (object) or bulk upsert by externalId (array)
Body: SessionInput | SessionInput[]
/api/v1/events/{event}/sessions/{id}Get
/api/v1/events/{event}/sessions/{id}Update
/api/v1/events/{event}/sessions/{id}Delete
/api/v1/events/{event}/sessions/importCSV import
Map
/api/v1/events/{event}/elementsList map elements (?levelId)
/api/v1/events/{event}/elementsCreate element
Body: { levelId, kind: wall|line|text|poi|zone|image|shape|entrance|stage|room, geometry, props }
/api/v1/events/{event}/elementsReplace a level's elements
Body: { levelId, elements[] }
/api/v1/events/{event}/elements/{id}Get
/api/v1/events/{event}/elements/{id}Update
/api/v1/events/{event}/elements/{id}Delete
Wayfinding
/api/v1/events/{event}/wayfindingNodes, edges, transitions (?levelId)
/api/v1/events/{event}/wayfindingReplace a level's network
Body: { levelId, nodes[], edges[] }
/api/v1/events/{event}/wayfinding/generateAuto-generate aisle network from geometry
Body: { levelId, cellSize?, clearance?, apply? }
/api/v1/events/{event}/wayfinding/transitionsList transitions
/api/v1/events/{event}/wayfinding/transitionsCreate transition
Body: { name, kind: stairs|escalator|elevator|ramp|door|bridge, accessible, nodeIds[], travelSeconds }
/api/v1/events/{event}/wayfinding/transitions/{id}Update
/api/v1/events/{event}/wayfinding/transitions/{id}Delete
/api/v1/events/{event}/routepublicPublic routing
Query: from (booth:A101 | exhibitor:slug | element:id | point:L1:x,y | A101)to (…)accessible (1)via (repeatable)
/api/v1/events/{event}/route/optimizepublicPublic multi-stop optimisation
Body: { start, stops[], accessible?, returnToStart? }
Sponsorship
/api/v1/events/{event}/bannersSponsor banners
/api/v1/events/{event}/bannersCreate banner
Body: { placement: search_top|list_inline|map_corner|detail_top|splash, imageUrl, linkUrl?, exhibitorId?, title?, weight?, startsAt?, endsAt?, active? }
/api/v1/events/{event}/banners/{id}Update
/api/v1/events/{event}/banners/{id}Delete
Analytics
/api/v1/events/{event}/analyticspublicPublic: ingest viewer analytics (sendBeacon)
Body: { events: [{ type: view|search|booth_click|exhibitor_view|category_select|route|bookmark|share|custom_button|banner_impression|banner_click|level_switch|position, sessionId?, targetType?, targetId?, query?, levelId?, x?, y?, meta? }] }
/api/v1/events/{event}/analytics/summaryAnalytics summary
Query: from (ISO)to (ISO)
/api/v1/events/{event}/analytics/exhibitors/{id}Per-exhibitor stats
Integrations
/api/v1/events/{event}/integrations/grip/syncSync exhibitors from Grip / a JSON or CSV feed
Body: { sourceUrl?, dryRun? }
Webhooks
/api/v1/webhooksList webhooks
/api/v1/webhooksCreate webhook (secret returned once)
Body: { url, eventId?, events?: ["*"] | (booth.created|booth.updated|booth.deleted|booth.status_changed|booth.assigned|booth.unassigned|booth.on_hold|exhibitor.created|exhibitor.updated|exhibitor.deleted|order.created|order.paid|order.cancelled|order.expired|floorplan.published|session.created|session.updated|session.deleted|extra.assigned|extra.removed)[], active? }
/api/v1/webhooks/{id}Update
/api/v1/webhooks/{id}Delete
/api/v1/webhooks/{id}/testSend a test delivery
/api/v1/webhooks/{id}/deliveriesRecent deliveries
/api/v1/webhooks/processDeliver pending/retrying webhooks now
Media
/api/v1/mediaUpload a file (multipart `file`) → { url }
/api/v1/geocodeVenue search → [{ name, address, lat, lng, kind }] (event wizard)
Query: q (venue name or address)
Compatibility
/api/v1/compat/expofp/{action}ExpoFP JSON API v1 compatibility shim (POST, token in body)
Actions: list-events, set-webhook-url, list-exhibitors, bulk-read-exhibitors, get-exhibitor, get-exhibitor-id, add-exhibitor, update-exhibitor, delete-exhibitor, set-exhibitor-logo, set-exhibitor-leading-image, list-booths, get-booth, update-booth, add-exhibitor-booth, remove-exhibitor-booth, list-categories, add-category, update-category, remove-category, list-extras, list-exhibitor-extras, add-exhibitor-extra, remove-exhibitor-extra, sessions/get, sessions/upsert, sessions/delete
Public
/e/{event}/data.jsonpublicPublic plan bundle (published)
Query: preview (1 (organiser session) for live data)
/e/{event}/data.expofp.jsonpublicPublic ExpoFP-compatible data.json
/e/{event}/version.jsonpublicPublished version probe