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:
{
"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:
curl http://localhost:8000/api/health{"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:
{
"success": false,
"data": null,
"message": "…",
"errors": { "email": ["…"] },
"code": "SEAT_UNAVAILABLE",
"details": { "seats": ["A12"] }
}messageis a sentence in the request's language.errorsappears on a validation failure, with the problems per field, keyed by the path the client sent (such astitle.enorattendee.email).- An unknown field in a body is refused with
400, anderrorsnames it. Sign-up, for one, takes only the fields listed below. codeanddetailsappear where a screen reacts to one refusal in particular:SEAT_UNAVAILABLEwith the seats taken,HOLD_EXPIRED,SOLD_OUT,ALREADY_ADMITTEDwith the first admission,VERSION_CONFLICT,NOT_READYwith what is missing,COOLDOWNwithretry_afterin seconds.- A guarded route with no token, or an expired one, answers
401; a token without the permission answers403. - A request over a limit answers
429.
Headers
| Header | Sent with | What it carries |
|---|---|---|
Authorization: Bearer … | Staff and attendee routes | The token from POST /api/auth/login (staff) or POST /api/customer-auth/login (attendee). The two kinds are not interchangeable |
X-Workspace-Id | Staff routes | The workspace the request is for. One the member does not belong to is ignored, and their own is used |
X-Hold-Token | A hold and the checkout made from it | The hold's secret, returned once when the hold is created |
X-Order-Token | A guest's order routes | The order's secret, returned once by POST /api/checkout. A signed-in attendee's own orders need only their token |
Idempotency-Key | POST /api/holds, POST /api/checkout, POST /api/admin/orders/:reference/refunds | A 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-Language | Any route | ar 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
{ "data": [ ], "page": 1, "limit": 10, "total": 36, "totalPages": 4 }- Most lists page with
pageandlimit, 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 returncountsper tab or status. - The team, roles and site settings lists page with
pageandpage_count, and the inquiries withpageandpage_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.namematches the first name, the last name, the username or the full name.
Signing in
Sign in as a staff member
Terminalcurl -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_tokenis the token, withtoken_typeBearerand the staff member with their roles and permissions. The token lastsJWT_EXPIRATION,7dby default, andexpires_inreports that value as written.GET /api/auth/mereturns the signed-in staff member with their roles and permissions.Call a protected route
Terminalcurl 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
401with the same message. - Ten failed sign-ins from one address within 15 minutes answer
429until 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/membershipslists the workspaces the member belongs to, their own first. Send the chosen one's id asX-Workspace-Id.
| Method | Path | Who can call it | What it does |
|---|---|---|---|
POST | /api/auth/login | Anyone | Staff sign-in: email, password |
GET | /api/auth/me | Any signed-in staff member | The signed-in staff member, their roles and permissions |
GET | /api/admin/workspace/memberships | Any signed-in staff member | The 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
| Method | Path | Who can call it | What it does |
|---|---|---|---|
GET | /api/public/events | Anyone | Upcoming 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/cities | Anyone | The cities with upcoming events and how many each has, for the city filter |
GET | /api/public/events/:slug | Anyone | One 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/availability | Anyone | Live 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/categories | Anyone | The event categories |
GET | /api/public/organisers | Anyone | The organisers |
GET | /api/public/organisers/:slug | Anyone | One organiser with up to 24 of their upcoming events and their total |
GET | /api/public/venues | Anyone | The venues, with their upcoming events and seating |
GET | /api/public/venues/:slug | Anyone | One venue, with its published seat layout |
GET | /api/public/settings | Anyone | What 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/public | Anyone | The site's site_name, contact_email and contact_city |
The organiser's takings are never part of a public answer.
Attendee accounts
| Method | Path | Who can call it | What it does |
|---|---|---|---|
POST | /api/customer-auth/register | Anyone | Sign 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/login | Anyone | Attendee sign-in: email, password, and remember for a 30-day token instead of a one-day one |
POST | /api/customer-auth/logout | Anyone | Answers { signed_out: true }; tokens are stateless, so the client forgets its copy |
POST | /api/customer-auth/forgot-password | Anyone | email. Sends a reset link valid for one hour when the account exists, and answers the same either way |
POST | /api/customer-auth/reset-password | Anyone | token (from the link) and the new password. An expired or used link answers 400 with TOKEN_EXPIRED |
POST | /api/customer-auth/verify-email | A signed-in attendee | code, 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-verification | A signed-in attendee | Send a new code; once a minute at most (429 with COOLDOWN and retry_after) |
GET | /api/me | A signed-in attendee | The attendee's profile |
PATCH | /api/me | A signed-in attendee | Change first_name, last_name, email, marketing_emails or reminder_emails |
GET | /api/me/saved | A signed-in attendee | Saved events, as cards |
GET | /api/me/saved/slugs | A signed-in attendee | The saved events' slugs only, for the heart buttons |
PUT | /api/me/saved/:slug | A signed-in attendee | Save an event |
DELETE | /api/me/saved/:slug | A signed-in attendee | Unsave an event |
GET | /api/me/tickets | A signed-in attendee | The attendee's tickets as upcoming and past, one card per order |
GET | /api/me/tickets/:reference | A signed-in attendee | One order's passes, with the QR codes and the download links |
GET | /api/me/orders | A signed-in attendee | The attendee's orders |
GET | /api/me/orders/:reference | A signed-in attendee | One 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.
| Method | Path | Who can call it | What it does |
|---|---|---|---|
POST | /api/holds | Anyone | Hold { 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/:id | The hold's owner, with X-Hold-Token | Read the hold |
PATCH | /api/holds/:id | The hold's owner, with X-Hold-Token | Replace the selection; the deadline does not move |
DELETE | /api/holds/:id | The hold's owner, with X-Hold-Token | Release the seats now |
GET | /api/checkout/methods | Anyone | The payment methods offered, each with provider and live |
POST | /api/checkout | The hold's owner, with X-Hold-Token | hold_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 answers410withHOLD_EXPIREDand the seats are free again. - A seat someone else holds or bought answers
409withSEAT_UNAVAILABLEand the seats indetails.seats; a full ticket type answersSOLD_OUT; more than the per-order limit answersTOO_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 providerfreeand statuspaid, andredirect_urlis its confirmation page.
Orders, ticket files and payments
| Method | Path | Who can call it | What it does |
|---|---|---|---|
GET | /api/orders/:reference/status | The order's attendee, or a guest with X-Order-Token | status (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/receipt | The order's attendee, or a guest with X-Order-Token | The 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-email | The order's attendee, or a guest with X-Order-Token | Send 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.pdf | Anyone holding the signed link | Every ticket of the order in one PDF |
GET | /api/orders/:reference/calendar.ics | Anyone holding the signed link | The event as a calendar file |
GET | /api/tickets/:reference/pdf | Anyone holding the signed link | One ticket as a PDF |
GET | /api/payments/return/paypal | Anyone | PayPal 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/complete | The simulator page, with the order's token | token and outcome (pay, fail or cancel). Answers 404 whenever the simulator is not offered |
POST | /api/webhooks/:provider | Stripe or PayPal | stripe 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
expandsigand work for 30 minutes from the receipt that made them; a stale one answers404, and reading the receipt again makes fresh ones. They are built fromPUBLIC_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_failedandrefund.updated; PayPalPAYMENT.CAPTURE.COMPLETED,PAYMENT.CAPTURE.DENIEDandPAYMENT.CAPTURE.REFUNDED. - The simulator is offered while no live Stripe or PayPal key is set;
PAYMENTS_SIMULATOR=onoroffoverrides that.
The contact form
| Method | Path | Who can call it | What it does |
|---|---|---|---|
POST | /api/leads | Anyone | name, 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_tokenreturns the first submission's receipt instead of storing a second one. - A filled
companyfield 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
| Method | Path | Who can call it | What it does |
|---|---|---|---|
GET | /api/admin/events | Staff with events.view | Events 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/:id | Staff with events.view | One event for the editor, with its readiness checklist, version and the local start and end |
POST | /api/admin/events | Staff with events.edit | Create 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/:id | Staff with events.edit | Save 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/publish | Staff with events.publish | Put a draft on sale. One that is not ready answers 400 with NOT_READY and what is missing |
POST | /api/admin/events/:id/unpublish | Staff with events.publish | Back to a draft: sales stop and holds are released, tickets already sold stay valid |
POST | /api/admin/events/:id/cancel | Staff 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/duplicate | Staff with events.edit | A new draft with the same details and ticket types, and no sales |
POST | /api/admin/events/:id/archive | Staff with events.publish | Archive a draft, or an event that has ended or been cancelled |
GET | /api/admin/events/:id/attendees | Staff with events.view | The event's tickets with holder, seat, type, status and admission time. q, status (admitted, not_admitted, void), page, limit |
Venues and seat layouts
| Method | Path | Who can call it | What it does |
|---|---|---|---|
GET | /api/admin/venues | Staff with venues.view | Venues. q, city, page, limit |
GET | /api/admin/venues/map | Staff with venues.view | Every venue's pin for the map |
GET | /api/admin/venues/:id | Staff with venues.view | One venue with its layouts |
POST | /api/admin/venues | Staff with venues.edit | Add a venue: name (en, ar), address, city, time_zone, capacity, accessibility (en, ar), and optionally slug, transit, location (latitude, longitude) |
PATCH | /api/admin/venues/:id | Staff with venues.edit | Change any of those fields |
GET | /api/admin/venues/:id/layouts | Staff with venues.view | The venue's layout versions |
GET | /api/admin/venues/:id/layouts/:version | Staff with venues.view | One version with its seats |
POST | /api/admin/venues/:id/layouts | Staff with venues.edit | Start a new draft |
PATCH | /api/admin/venues/:id/layouts/:version | Staff with venues.edit | Save 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/publish | Staff 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
| Method | Path | Who can call it | What it does |
|---|---|---|---|
GET | /api/admin/orders | Staff with orders.view | Orders 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/events | Staff with orders.view | Every event that has orders, for the orders page's filter |
GET | /api/admin/orders/:reference | Staff with orders.view | One order: tickets, refunds, the payment timeline, a link to it in the provider's dashboard, and its version |
GET | /api/admin/orders/:reference/refund-preview | Staff with refunds.create | What refunding ?tickets=TKT-1042-2,… would return, per ticket |
POST | /api/admin/orders/:reference/refunds | Staff with refunds.create | Refund 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/retry | Staff with refunds.create | Send a failed refund to the provider again |
POST | /api/admin/refunds/:id/restore | Staff with refunds.create | Close a failed refund for good; its tickets are valid again |
POST | /api/admin/tickets/:reference/reissue | Staff with tickets.manage | A new QR code for a valid ticket; the old code stops working at the door |
POST | /api/admin/tickets/:reference/void | Staff with tickets.manage | Void 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
| Method | Path | Who can call it | What it does |
|---|---|---|---|
GET | /api/admin/check-in/sessions | Staff with checkin.scan | The 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/summary | Staff with checkin.scan | Admitted, expected, remaining, and the alerts waiting |
GET | /api/admin/check-in/sessions/:code/scans | Staff with checkin.scan | The recent scans, each admitted, eligible, duplicate, wrong_session, void or not_found |
POST | /api/admin/check-in/sessions/:code/acknowledge | Staff with checkin.scan | Mark the session's alerts as seen |
POST | /api/admin/check-in/validate | Staff with checkin.scan | session_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/admit | Staff with checkin.scan | The 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-staff | Staff with admins.edit | Which check-in staff are assigned to which sessions |
PUT | /api/admin/session-staff/:code | Staff with admins.edit | Set 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
| Method | Path | Who can call it | What it does |
|---|---|---|---|
GET | /api/admin/overview | Staff with overview.view | The 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/reports | Staff with reports.view | The same sales figures, for range, or from and to, and optionally one event |
POST | /api/admin/reports/export | Staff with reports.export | Start a CSV: kind (orders, sales, attendees) and optional filters. Answers 202 with its id |
GET | /api/admin/reports/exports/:id | Staff with reports.export | Where the export has got to; once ready, its download_url |
GET | /api/admin/reports/exports/:id/download | Anyone holding the link's token | The 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
| Method | Path | Who can call it | What it does |
|---|---|---|---|
GET | /api/admin/workspace | Staff with settings.view | The 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/workspace | Staff with settings.edit | Change 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-providers | Staff with settings.view | Each provider's configured, live and offered. Keys stay in the API's .env and are never returned |
GET | /api/settings | Staff with settings.view | Every site setting. search, category, page, page_count |
GET | /api/settings/:key | Staff with settings.view | One setting by key |
PATCH | /api/settings/:key | Staff with settings.edit | Change one setting's value |
DELETE | /api/settings/:key | Staff with settings.edit | Delete one setting |
Team and roles
| Method | Path | Who can call it | What it does |
|---|---|---|---|
GET | /api/admins | Staff with admins.view | The workspace's team. email, name, phone (all that are given must match), page, page_count |
GET | /api/admins/statistics | Staff with admins.view | Counts for the team list |
GET | /api/admins/:id | Staff with admins.view | One member, by id or username |
POST | /api/admins | Staff with admins.create | Add a member: first_name, last_name, email, password and password_confirmation (6 characters or more), optionally phone |
PATCH | /api/admins/:id | Staff with admins.edit | Change a member |
PATCH | /api/admins/:id/roles | Staff with admins.assign_roles | Set a member's roles: role_ids |
DELETE | /api/admins/:id | Staff with admins.delete | Take 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/profile | Any signed-in staff member | Change 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/password | Any signed-in staff member | Change the signed-in member's own password: current_password, password, password_confirmation |
GET | /api/roles | Staff with roles.view | Roles. name, guard_name, created_from, created_to, page, page_count |
GET | /api/roles/statistics | Staff with roles.view | Counts for the roles list |
GET | /api/roles/select | Staff with roles.view | Every role's id and name, for a picker |
GET | /api/roles/permissions | Staff with roles.view | Every permission, grouped by area |
GET | /api/roles/:id | Staff with roles.view | One role with its permissions |
POST | /api/roles | Staff with roles.create | Add a role: name |
PUT | /api/roles/:id | Staff with roles.edit | Rename a role: name |
POST | /api/roles/:id/permissions | Staff with roles.assign_permissions | Set a role's permissions, by name |
DELETE | /api/roles/:id | Staff with roles.delete | Delete a role |
Inquiries, notifications and uploads
| Method | Path | Who can call it | What it does |
|---|---|---|---|
GET | /api/leads | Staff with leads.view | Inquiries from the contact form, with counts per status. status, q, received_within_days, page, page_size |
GET | /api/leads/:id | Staff with leads.view | One inquiry |
PATCH | /api/leads/:id | Staff with leads.edit | Set status (new, contacted, closed), notes or assigned_admin_id |
GET | /api/notifications | Any signed-in staff member | The signed-in member's newest 30 notifications |
PATCH | /api/notifications/read-all | Any signed-in staff member | Mark them all read |
PATCH | /api/notifications/:id/read | Any signed-in staff member | Mark one read |
DELETE | /api/notifications/:id | Any signed-in staff member | Remove one |
POST | /api/helpers/upload | Any signed-in staff member | Store 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
| Route | Limit | Variable |
|---|---|---|
POST /api/auth/login, POST /api/customer-auth/login | 10 failed sign-ins per 15 minutes, each counted apart | RATE_LIMIT_LOGIN |
POST /api/customer-auth/register | 10 per 10 minutes | None |
POST /api/customer-auth/forgot-password | 5 per 10 minutes | None |
POST /api/leads | 5 per 10 minutes | None |
POST /api/admin/check-in/validate, /admit | 120 per minute per staff member | None |
POST /api/customer-auth/resend-verification, POST /api/orders/:reference/resend-email | Once a minute | None |
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.