Skip to the article
Aniq-UI

TicketoryAPI reference

API reference

Every route the API serves, who may call it, and how sign-in, permissions, workspaces, holds, payments, errors, lists and limits work.

For the Full Stack package

Base URL and response format

Every route is served under the /api prefix. Locally the base URL is http://localhost:8000/api. On a server it is your API's address followed by /api, the same value both frontends read from NEXT_PUBLIC_API_BASE_URL (Environment variables). Pictures are served outside the prefix, at /media/….

The website's routes are public or take an attendee's token; the dashboard's need a staff member's bearer token, and most also a permission. Request bodies are JSON, except file uploads. Every answer comes in the same envelope:

Envelope
{
  "success": true,
  "data": { },
  "message": ""
}

data holds the result. message is a short sentence some writes fill in, in the request's language, and empty otherwise. Money is a whole number of minor units with its currency (price_minor: 2000 is 20.00), and every date is an ISO string in UTC; each event also carries its time_zone.

The health check needs no token:

Terminal
curl http://localhost:8000/api/health
Response
{"success":true,"data":{"status":"ok","database":"up"},"message":""}

When the database cannot be read it still answers 200, with "status":"degraded" and "database":"down". The Docker image's health check reads that field.

Errors

A failure keeps the envelope, with success: false and the HTTP status:

Error
{
  "success": false,
  "data": null,
  "message": "…",
  "errors": { "email": ["…"] },
  "code": "SEAT_UNAVAILABLE",
  "details": { "seats": ["A12"] }
}
  • message is a sentence in the request's language.
  • errors appears on a validation failure, with the problems per field, keyed by the path the client sent (such as title.en or attendee.email).
  • An unknown field in a body is refused with 400, and errors names it. Sign-up, for one, takes only the fields listed below.
  • code and details appear where a screen reacts to one refusal in particular: SEAT_UNAVAILABLE with the seats taken, HOLD_EXPIRED, SOLD_OUT, ALREADY_ADMITTED with the first admission, VERSION_CONFLICT, NOT_READY with what is missing, COOLDOWN with retry_after in seconds.
  • A guarded route with no token, or an expired one, answers 401; a token without the permission answers 403.
  • A request over a limit answers 429.

Headers

HeaderSent withWhat it carries
Authorization: Bearer …Staff and attendee routesThe token from POST /api/auth/login (staff) or POST /api/customer-auth/login (attendee). The two kinds are not interchangeable
X-Workspace-IdStaff routesThe workspace the request is for. One the member does not belong to is ignored, and their own is used
X-Hold-TokenA hold and the checkout made from itThe hold's secret, returned once when the hold is created
X-Order-TokenA guest's order routesThe order's secret, returned once by POST /api/checkout. A signed-in attendee's own orders need only their token
Idempotency-KeyPOST /api/holds, POST /api/checkout, POST /api/admin/orders/:reference/refundsA UUID made when the form opens. A retry with the same key and body returns the first answer instead of doing the work twice; without one the request is refused
Accept-LanguageAny routear for Arabic messages; anything else answers in English

Event, venue and ticket type texts carry both languages as { "en": "…", "ar": "…" } whatever Accept-Language says, and the client picks one, falling back to English when the Arabic is empty.

Lists and paging

A list
{ "data": [ ], "page": 1, "limit": 10, "total": 36, "totalPages": 4 }
  • Most lists page with page and limit, at most 100 a page: 9 events a page on the website, 10 events, venues and attendees and 8 orders in the dashboard by default. Several also return counts per tab or status.
  • The team, roles and site settings lists page with page and page_count, and the inquiries with page and page_size (10 by default, at most 50).
  • The dashboard's lists come newest-updated first, with the id as a tiebreaker, so paging is stable. The website's catalogue comes soonest first.
  • The team list's filters (email, name, phone) combine: a row must match every one given. name matches the first name, the last name, the username or the full name.

Signing in

  1. Sign in as a staff member

    Terminal
    curl -X POST http://localhost:8000/api/auth/login \
      -H "Content-Type: application/json" \
      -d '{"email":"YOUR_EMAIL","password":"YOUR_PASSWORD"}'

    Use one of the dashboard accounts from Installation, or your own. data.access_token is the token, with token_type Bearer and the staff member with their roles and permissions. The token lasts JWT_EXPIRATION, 7d by default, and expires_in reports that value as written. GET /api/auth/me returns the signed-in staff member with their roles and permissions.

  2. Call a protected route

    Terminal
    curl http://localhost:8000/api/admin/orders \
      -H "Authorization: Bearer YOUR_TOKEN" \
      -H "X-Workspace-Id: 1"
  • An unknown email and a wrong password both answer 401 with the same message.
  • Ten failed sign-ins from one address within 15 minutes answer 429 until the oldest failure is 15 minutes old (RATE_LIMIT_LOGIN). Successful sign-ins are not counted. Staff and attendee sign-ins are counted apart.
  • Staff accounts come from the seed or from the Team page; there is no staff sign-up. A client signs out by forgetting its token.
  • GET /api/admin/workspace/memberships lists the workspaces the member belongs to, their own first. Send the chosen one's id as X-Workspace-Id.
MethodPathWho can call itWhat it does
POST/api/auth/loginAnyoneStaff sign-in: email, password
GET/api/auth/meAny signed-in staff memberThe signed-in staff member, their roles and permissions
GET/api/admin/workspace/membershipsAny signed-in staff memberThe member's workspaces, for the navbar's selector

Permissions

Staff routes check a permission named <area>.<action>. A member holds every permission of every role they have, and the role is the same in every workspace they belong to. The 29 permissions and the four seeded roles (Owner, Manager, Finance, Check-in) are listed in Team, roles and workspaces.

Events, venues and organisers on the website

MethodPathWho can call itWhat it does
GET/api/public/eventsAnyoneUpcoming public events, soonest first. q (title, summary, venue, city or organiser), category, city, venue and organiser (slugs), from and to (YYYY-MM-DD), page, limit. Drafts, unlisted events and events that have ended are left out
GET/api/public/events/citiesAnyoneThe cities with upcoming events and how many each has, for the city filter
GET/api/public/events/:slugAnyoneOne event in full: description, agenda, the venue with its address, accessibility, transit and location, the session code, and the ticket types with price, fee, places left and the per-order limit. A cancelled event answers with status: cancelled
GET/api/public/sessions/:code/availabilityAnyoneLive availability for the seat map: the server's clock, hold_seconds, max_per_order, the fee, the ticket types, the published seat layout (or null for general admission) and every seat as available, held, sold or unavailable
GET/api/public/categoriesAnyoneThe event categories
GET/api/public/organisersAnyoneThe organisers
GET/api/public/organisers/:slugAnyoneOne organiser with up to 24 of their upcoming events and their total
GET/api/public/venuesAnyoneThe venues, with their upcoming events and seating
GET/api/public/venues/:slugAnyoneOne venue, with its published seat layout
GET/api/public/settingsAnyoneWhat the website needs to price and time a purchase: the workspace's name, currency, fee_bps (the booking fee the attendee pays on each ticket, 300 is 3%), hold_seconds, max_per_order, time_zone, support_email, payment_methods and mail_simulated (true while emails are written to the API's log)
GET/api/settings/publicAnyoneThe site's site_name, contact_email and contact_city

The organiser's takings are never part of a public answer.

Attendee accounts

MethodPathWho can call itWhat it does
POST/api/customer-auth/registerAnyoneSign up: first_name, last_name, email, password (8 to 72 characters with a letter and a digit), terms_accepted (true), and optionally role (attendee or organiser, never read). Any other field answers 400 naming it. Answers 201, signed in at once, and sends a six-digit code
POST/api/customer-auth/loginAnyoneAttendee sign-in: email, password, and remember for a 30-day token instead of a one-day one
POST/api/customer-auth/logoutAnyoneAnswers { signed_out: true }; tokens are stateless, so the client forgets its copy
POST/api/customer-auth/forgot-passwordAnyoneemail. Sends a reset link valid for one hour when the account exists, and answers the same either way
POST/api/customer-auth/reset-passwordAnyonetoken (from the link) and the new password. An expired or used link answers 400 with TOKEN_EXPIRED
POST/api/customer-auth/verify-emailA signed-in attendeecode, the six digits from the email, valid for 24 hours. Orders bought with that address as a guest join the account
POST/api/customer-auth/resend-verificationA signed-in attendeeSend a new code; once a minute at most (429 with COOLDOWN and retry_after)
GET/api/meA signed-in attendeeThe attendee's profile
PATCH/api/meA signed-in attendeeChange first_name, last_name, email, marketing_emails or reminder_emails
GET/api/me/savedA signed-in attendeeSaved events, as cards
GET/api/me/saved/slugsA signed-in attendeeThe saved events' slugs only, for the heart buttons
PUT/api/me/saved/:slugA signed-in attendeeSave an event
DELETE/api/me/saved/:slugA signed-in attendeeUnsave an event
GET/api/me/ticketsA signed-in attendeeThe attendee's tickets as upcoming and past, one card per order
GET/api/me/tickets/:referenceA signed-in attendeeOne order's passes, with the QR codes and the download links
GET/api/me/ordersA signed-in attendeeThe attendee's orders
GET/api/me/orders/:referenceA signed-in attendeeOne order's receipt, with its timeline

Sign-up is limited to 10 per address in 10 minutes, and password reset requests to 5. While no mail key is set, codes and links are written to the API's log, and mail_simulated says so.

Holds and checkout

A purchase is two calls: hold the seats or places, then check out from the hold. Both work for a guest and for a signed-in attendee, whose token is read when sent.

MethodPathWho can call itWhat it does
POST/api/holdsAnyoneHold { session_code, seats: ["A12", "B12"] } for reserved seating, or { session_code, ticket_type_id, quantity } for general admission, 1 to 8 at a time. Needs Idempotency-Key. Answers 201 with the hold, its expires_at and the server's clock, and its token, shown once
GET/api/holds/:idThe hold's owner, with X-Hold-TokenRead the hold
PATCH/api/holds/:idThe hold's owner, with X-Hold-TokenReplace the selection; the deadline does not move
DELETE/api/holds/:idThe hold's owner, with X-Hold-TokenRelease the seats now
GET/api/checkout/methodsAnyoneThe payment methods offered, each with provider and live
POST/api/checkoutThe hold's owner, with X-Hold-Tokenhold_id, attendee (first_name, last_name, email), provider (stripe, paypal or simulated) and terms_accepted. Needs Idempotency-Key. Answers 201 with order_reference, status, redirect_url and access_token, the order's secret, shown once. The request's Accept-Language (ar, otherwise en) is kept on the order as locale, the language of every email about it
  • A hold lasts the workspace's hold time (hold_seconds, ten minutes as seeded), counted on the server's clock. After it, the hold answers 410 with HOLD_EXPIRED and the seats are free again.
  • A seat someone else holds or bought answers 409 with SEAT_UNAVAILABLE and the seats in details.seats; a full ticket type answers SOLD_OUT; more than the per-order limit answers TOO_MANY.
  • Send the browser to redirect_url: Stripe's or PayPal's own page, or the website's simulator page. Card details are never sent to this API.
  • A hold whose total is 0 needs no provider: the order is confirmed at once with provider free and status paid, and redirect_url is its confirmation page.

Orders, ticket files and payments

MethodPathWho can call itWhat it does
GET/api/orders/:reference/statusThe order's attendee, or a guest with X-Order-Tokenstatus (pending_payment, paid, payment_failed, expired, payment_review, partially_refunded, refunded), the provider and the server's clock, for the page that waits for the payment
GET/api/orders/:reference/receiptThe order's attendee, or a guest with X-Order-TokenThe order with its event, venue, tickets (each paid ticket with its QR code), the ticket email's state and the signed downloads links
POST/api/orders/:reference/resend-emailThe order's attendee, or a guest with X-Order-TokenSend the tickets again to the order's address, in the language of the request's Accept-Language. Answers 202, or 429 with COOLDOWN within a minute of the last one
GET/api/orders/:reference/tickets.pdfAnyone holding the signed linkEvery ticket of the order in one PDF
GET/api/orders/:reference/calendar.icsAnyone holding the signed linkThe event as a calendar file
GET/api/tickets/:reference/pdfAnyone holding the signed linkOne ticket as a PDF
GET/api/payments/return/paypalAnyonePayPal sends the buyer here: the API captures the payment and redirects to the website's waiting page (or its cancelled page)
POST/api/payments/simulated/:reference/completeThe simulator page, with the order's tokentoken and outcome (pay, fail or cancel). Answers 404 whenever the simulator is not offered
POST/api/webhooks/:providerStripe or PayPalstripe or paypal. The signature is checked against STRIPE_WEBHOOK_SECRET or PAYPAL_WEBHOOK_ID; a missing or wrong one answers 401. Each event is handled once, however often it is sent
  • The download links are signed with exp and sig and work for 30 minutes from the receipt that made them; a stale one answers 404, and reading the receipt again makes fresh ones. They are built from PUBLIC_API_URL.
  • Tickets are issued once the provider confirms the payment, by its webhook, by PayPal's return, or by the simulator. Stripe sends checkout.session.completed, checkout.session.async_payment_succeeded, checkout.session.async_payment_failed and refund.updated; PayPal PAYMENT.CAPTURE.COMPLETED, PAYMENT.CAPTURE.DENIED and PAYMENT.CAPTURE.REFUNDED.
  • The simulator is offered while no live Stripe or PayPal key is set; PAYMENTS_SIMULATOR=on or off overrides that.

The contact form

MethodPathWho can call itWhat it does
POST/api/leadsAnyonename, email, subject (general, tickets, organisers, scale, press), message (10 to 2000 characters) and client_token (a UUID v4). Answers 201 with { id, received_at }
  • A repeated client_token returns the first submission's receipt instead of storing a second one.
  • A filled company field is treated as a bot: the answer looks the same, and nothing is stored.
  • Each address may send five in ten minutes; the next answers 429.

Events

MethodPathWho can call itWhat it does
GET/api/admin/eventsStaff with events.viewEvents with counts per tab. tab (all, published, draft, ended), q, category, status, venue (id), from, to, sort (starts_at, else last updated), order (asc, desc), page, limit
GET/api/admin/events/:idStaff with events.viewOne event for the editor, with its readiness checklist, version and the local start and end
POST/api/admin/eventsStaff with events.editCreate a draft: title, summary, description (each en, ar), category (slug), venue_id, color (pink, yellow, teal, blue), admission_mode (reserved, general), visibility (public, unlisted), starts_local, ends_local, time_zone, agenda and ticket_types (title, price_minor, capacity, and optionally description, max_per_order, status), optionally slug
PATCH/api/admin/events/:idStaff with events.editSave the same fields with the version the editor loaded. A newer save answers 409 with VERSION_CONFLICT; a sold ticket type's price is locked (PRICE_LOCKED)
POST/api/admin/events/:id/publishStaff with events.publishPut a draft on sale. One that is not ready answers 400 with NOT_READY and what is missing
POST/api/admin/events/:id/unpublishStaff with events.publishBack to a draft: sales stop and holds are released, tickets already sold stay valid
POST/api/admin/events/:id/cancelStaff with events.publish{ confirm }, the event's slug typed out. Sales and admission stop, and every paid order's holder is emailed. Refunds are then made per order
POST/api/admin/events/:id/duplicateStaff with events.editA new draft with the same details and ticket types, and no sales
POST/api/admin/events/:id/archiveStaff with events.publishArchive a draft, or an event that has ended or been cancelled
GET/api/admin/events/:id/attendeesStaff with events.viewThe event's tickets with holder, seat, type, status and admission time. q, status (admitted, not_admitted, void), page, limit

Venues and seat layouts

MethodPathWho can call itWhat it does
GET/api/admin/venuesStaff with venues.viewVenues. q, city, page, limit
GET/api/admin/venues/mapStaff with venues.viewEvery venue's pin for the map
GET/api/admin/venues/:idStaff with venues.viewOne venue with its layouts
POST/api/admin/venuesStaff with venues.editAdd a venue: name (en, ar), address, city, time_zone, capacity, accessibility (en, ar), and optionally slug, transit, location (latitude, longitude)
PATCH/api/admin/venues/:idStaff with venues.editChange any of those fields
GET/api/admin/venues/:id/layoutsStaff with venues.viewThe venue's layout versions
GET/api/admin/venues/:id/layouts/:versionStaff with venues.viewOne version with its seats
POST/api/admin/venues/:id/layoutsStaff with venues.editStart a new draft
PATCH/api/admin/venues/:id/layouts/:versionStaff with venues.editSave the draft: rows (1 to 40), seats_per_row (1 to 60), aisles_after (up to 6), seats (label, and optionally accessible, companion, disabled, price_tier 0 to 9) and loaded_at. A draft saved by someone else since answers 409 with VERSION_CONFLICT
POST/api/admin/venues/:id/layouts/:version/publishStaff with venues.edit{ acknowledged: true }. Publishes the draft as the venue's layout; seats already sold keep the version they were sold on. An empty layout, or more seats than the venue's capacity (OVER_CAPACITY), is refused

Orders, tickets and refunds

MethodPathWho can call itWhat it does
GET/api/admin/ordersStaff with orders.viewOrders with counts per status. status (paid, pending_payment, refunded, payment_failed, payment_review), event (slug), provider (stripe, paypal, simulated, free), q (reference, email or name), from, to, page, limit
GET/api/admin/orders/eventsStaff with orders.viewEvery event that has orders, for the orders page's filter
GET/api/admin/orders/:referenceStaff with orders.viewOne order: tickets, refunds, the payment timeline, a link to it in the provider's dashboard, and its version
GET/api/admin/orders/:reference/refund-previewStaff with refunds.createWhat refunding ?tickets=TKT-1042-2,… would return, per ticket
POST/api/admin/orders/:reference/refundsStaff with refunds.createRefund whole tickets: tickets (1 to 8 references), reason (customer_request, event_cancelled, duplicate_order, event_changed, other), acknowledged (true) and order_version. Needs Idempotency-Key. Answers 202 with the refund pending
POST/api/admin/refunds/:id/retryStaff with refunds.createSend a failed refund to the provider again
POST/api/admin/refunds/:id/restoreStaff with refunds.createClose a failed refund for good; its tickets are valid again
POST/api/admin/tickets/:reference/reissueStaff with tickets.manageA new QR code for a valid ticket; the old code stops working at the door
POST/api/admin/tickets/:reference/voidStaff with tickets.manageVoid a free ticket. A paid ticket answers 409 with USE_REFUND: it is refunded instead

Its tickets are suspended (refund_pending) as soon as the refund is asked for, so the door refuses them. The refund settles when the provider confirms it, a few seconds later for the simulator: the tickets are then voided and their seats go back on sale. A failed refund keeps them suspended until it is retried, or restored, which makes them valid again. Read the order again to see where it stands.

Check-in at the door

MethodPathWho can call itWhat it does
GET/api/admin/check-in/sessionsStaff with checkin.scanThe sessions this member may scan, from a day ago onwards, with admitted and expected counts and whether the doors are open
GET/api/admin/check-in/sessions/:code/summaryStaff with checkin.scanAdmitted, expected, remaining, and the alerts waiting
GET/api/admin/check-in/sessions/:code/scansStaff with checkin.scanThe recent scans, each admitted, eligible, duplicate, wrong_session, void or not_found
POST/api/admin/check-in/sessions/:code/acknowledgeStaff with checkin.scanMark the session's alerts as seen
POST/api/admin/check-in/validateStaff with checkin.scansession_code and code: what the camera read, or a typed ticket reference such as TKT-1042-1. Says whether the ticket may enter, without admitting it
POST/api/admin/check-in/admitStaff with checkin.scanThe same with client_request_id (a UUID), and admits the ticket. A ticket already in answers 409 with ALREADY_ADMITTED, when and by whom; WRONG_SESSION and TICKET_VOID are refused too
GET/api/admin/session-staffStaff with admins.editWhich check-in staff are assigned to which sessions
PUT/api/admin/session-staff/:codeStaff with admins.editSet a session's door staff: admin_ids, comma separated

Members who can edit or publish events reach every session; the others only the sessions they are assigned to, and another answers 403. Each member may make 120 checks a minute.

Overview, reports and exports

MethodPathWho can call itWhat it does
GET/api/admin/overviewStaff with overview.viewThe overview: totals, the sales series, sales by event, provider and category, the live door, the next events and what needs attention. range (last_7_days, last_30_days, this_month, all), last_30_days by default
GET/api/admin/reportsStaff with reports.viewThe same sales figures, for range, or from and to, and optionally one event
POST/api/admin/reports/exportStaff with reports.exportStart a CSV: kind (orders, sales, attendees) and optional filters. Answers 202 with its id
GET/api/admin/reports/exports/:idStaff with reports.exportWhere the export has got to; once ready, its download_url
GET/api/admin/reports/exports/:id/downloadAnyone holding the link's tokenThe CSV file. The link works for one hour and is shown once

CSV files are written to EXPORT_DIR in the background, and the download link is built from PUBLIC_API_URL.

Workspace, payments and site settings

MethodPathWho can call itWhat it does
GET/api/admin/workspaceStaff with settings.viewThe workspace: name, support email, currency, time zone, booking fee (fee_bps), hold_seconds and max_per_order (read only: they are columns seeded from back-end/src/database/seeders/data/settings.json), and notification_settings, the switches for the team's notification bell
PATCH/api/admin/workspaceStaff with settings.editChange name, support_email, currency (USD, EUR, GBP, AED, SAR), time_zone, fee_bps (0 to 2000, hundredths of a percent; the attendee always pays it at checkout) or notification_settings (order_paid, refund_requested, checkin_alerts), with the version loaded. The currency locks once the workspace has sold (CURRENCY_LOCKED). Any other field, fee_payer, hold_seconds and max_per_order included, answers 400
GET/api/admin/payment-providersStaff with settings.viewEach provider's configured, live and offered. Keys stay in the API's .env and are never returned
GET/api/settingsStaff with settings.viewEvery site setting. search, category, page, page_count
GET/api/settings/:keyStaff with settings.viewOne setting by key
PATCH/api/settings/:keyStaff with settings.editChange one setting's value
DELETE/api/settings/:keyStaff with settings.editDelete one setting

Team and roles

MethodPathWho can call itWhat it does
GET/api/adminsStaff with admins.viewThe workspace's team. email, name, phone (all that are given must match), page, page_count
GET/api/admins/statisticsStaff with admins.viewCounts for the team list
GET/api/admins/:idStaff with admins.viewOne member, by id or username
POST/api/adminsStaff with admins.createAdd a member: first_name, last_name, email, password and password_confirmation (6 characters or more), optionally phone
PATCH/api/admins/:idStaff with admins.editChange a member
PATCH/api/admins/:id/rolesStaff with admins.assign_rolesSet a member's roles: role_ids
DELETE/api/admins/:idStaff with admins.deleteTake a member out of the current workspace. The account itself is deleted only when that was its last workspace; data.removed answers workspace or account. Removing yourself answers 403 with REMOVE_SELF, and removing the workspace's last Owner 409 with LAST_OWNER
PATCH/api/admins/profileAny signed-in staff memberChange the signed-in member's own first_name, last_name, email, phone, profile_picture or country_id. A password or role_ids in the body answers 400: the password has its own route
PATCH/api/admins/profile/passwordAny signed-in staff memberChange the signed-in member's own password: current_password, password, password_confirmation
GET/api/rolesStaff with roles.viewRoles. name, guard_name, created_from, created_to, page, page_count
GET/api/roles/statisticsStaff with roles.viewCounts for the roles list
GET/api/roles/selectStaff with roles.viewEvery role's id and name, for a picker
GET/api/roles/permissionsStaff with roles.viewEvery permission, grouped by area
GET/api/roles/:idStaff with roles.viewOne role with its permissions
POST/api/rolesStaff with roles.createAdd a role: name
PUT/api/roles/:idStaff with roles.editRename a role: name
POST/api/roles/:id/permissionsStaff with roles.assign_permissionsSet a role's permissions, by name
DELETE/api/roles/:idStaff with roles.deleteDelete a role

Inquiries, notifications and uploads

MethodPathWho can call itWhat it does
GET/api/leadsStaff with leads.viewInquiries from the contact form, with counts per status. status, q, received_within_days, page, page_size
GET/api/leads/:idStaff with leads.viewOne inquiry
PATCH/api/leads/:idStaff with leads.editSet status (new, contacted, closed), notes or assigned_admin_id
GET/api/notificationsAny signed-in staff memberThe signed-in member's newest 30 notifications
PATCH/api/notifications/read-allAny signed-in staff memberMark them all read
PATCH/api/notifications/:id/readAny signed-in staff memberMark one read
DELETE/api/notifications/:idAny signed-in staff memberRemove one
POST/api/helpers/uploadAny signed-in staff memberStore a file (multipart file, with path for the folder, for for the size preset such as venue, cover, logo or profile, and type). Photos are re-encoded, at most 1920 pixels wide; videos are accepted as MP4, WebM, MOV or M4V; 150 MB at most. Answers the stored file's addresses

Notifications cover paid orders, refund requests, door alerts, new inquiries and finished venue concepts, and are kept for 30 days. An upload has no permission of its own: what the file becomes is checked by the route that saves it. Files go to the R2 bucket when its five variables are set, and to MEDIA_UPLOAD_DIR otherwise.

Limits per address

RouteLimitVariable
POST /api/auth/login, POST /api/customer-auth/login10 failed sign-ins per 15 minutes, each counted apartRATE_LIMIT_LOGIN
POST /api/customer-auth/register10 per 10 minutesNone
POST /api/customer-auth/forgot-password5 per 10 minutesNone
POST /api/leads5 per 10 minutesNone
POST /api/admin/check-in/validate, /admit120 per minute per staff memberNone
POST /api/customer-auth/resend-verification, POST /api/orders/:reference/resend-emailOnce a minuteNone

A request over a limit answers 429. The counts live in the API's memory, so a restart clears them. Behind a reverse proxy, set TRUST_PROXY so each visitor is counted on their own address.

AI event assistant and its conversations

Included with your purchase. Sign in to read, or open it in your download.

The assistant's streaming route, its model list, draft approval and the saved conversations.

Venue studio

Included with your purchase. Sign in to read, or open it in your download.

The studio's routes: generations and the concepts saved on a venue.

MCP server

Included with your purchase. Sign in to read, or open it in your download.

The MCP endpoint for coding agents and how it is authorised.

Live updates over WebSocket

Included with your purchase. Sign in to read, or open it in your download.

The Socket.IO namespaces the dashboard listens on, their events and how a connection is authorised.

Demo mode routes

Included with your purchase. Sign in to read, or open it in your download.

The routes that exist only for a public demo build.

Stuck on a step?

Find a fix before you start over.

Troubleshooting

Cookie Preferences

We use cookies to enhance your browsing experience, analyze site traffic, and personalize content. By clicking "Accept All", you consent to our use of cookies for analytics and personalized advertising.