Public API and MCP reference
Integration reference for event discovery, ticket checkout and MCP access, with request formats, response fields and error handling.
Gotobeat provides public event data over HTTP, signed ticket checkout through the Agentic Commerce Protocol (ACP), and public and authenticated Model Context Protocol (MCP) tools.
Discovery files
| File | What it is |
|---|---|
| /llms.txt | Entry points for agents. |
| /llms-full.txt | Endpoint summaries, ticket identifiers and checkout flow. |
| /openapi/agents.json | OpenAPI 3.1 schemas for the public HTTP API. |
| /docs/agents/ | Integration guide for checkout and MCP authentication. |
/sitemap.xml indexes five sections: events-upcoming, events-archive,
artists, venues and content. Each section has a six-hour edge cache lifetime.
/.well-known/security.txt lists the security contact.
Page URLs use a trailing slash; requests without it receive a 301 redirect.
Public HTTP routes
The documented API uses the following prefixes, which robots.txt allows under /api/.
Crawler rules describe indexing policy; each endpoint enforces its own access checks.
Use the endpoints listed in this reference for integrations.
| Prefix | Purpose |
|---|---|
/api/events |
Event listing and detail |
/api/search/ |
Event keyword search |
/api/artists/search |
Artist search |
/api/venues/search |
Venue search |
/api/checkout_sessions |
Signed ACP checkout |
Event response fields
The three HTTP event endpoints use the same field projection. Optional values are omitted
when absent. hideDateTime omits dates and times; hideVenue omits the venue object.
| Field | Type | Notes |
|---|---|---|
eventId, slug, url |
string | url is the canonical gig page |
title, description |
string | description contains visible text with HTML removed |
status |
string | PUBLISHED in these HTTP responses |
date, untilDate |
YYYY-MM-DD |
untilDate only on a multi-day event |
startTime, endTime, doorTime |
HH:MM |
venue local time |
hideDateTime, hideVenue |
boolean | when true, the hidden fields are omitted |
genres |
string[] | |
image |
string | the poster URL |
ageLimit |
string | |
artists[] |
object | name, slug, image, genres[], spotifyUrl |
venue |
object | name, address, city, postcode, country, googleMapsUrl |
tickets[] |
object | see the tier table below |
externalTickets[] |
object | name, url, when another ticketer sells the show |
Ticket tier fields:
| Field | Type | Notes |
|---|---|---|
sku |
string | <eventId>:<ticketTypeId>, the id a checkout session takes |
ticketTypeId |
string | |
name |
string | |
price |
decimal string | for example, "22.50" |
currency |
ISO 4217 | GBP, EUR, or the venue's currency |
availability |
enum | InStock, SoldOut or PreSale |
releaseDate |
ISO 8601 | optional scheduled release time |
minQuantity, maxQuantity |
integer | optional limits per order |
Read endpoints
These five GET endpoints accept anonymous requests. HTTP event results are restricted to
upcoming PUBLISHED events that carry no password. The detail endpoint returns 404 for an
unknown slug and for an event outside that filter, with the same body in both cases, so a
response discloses no existence signal.
A password-gated event is refused by the read itself. One helper,
isUpcomingPublishedEvent, answers status, date and password together, and every anonymous
read applies it: /api/events and both of its search fallbacks, /api/events/:slug,
/api/search/event/:keyword, the /api/events/list.csv product feed, and the two listing
MCP tools. public_get_event reads the same status and password rules directly rather than
through the helper, so it can still answer a finished show — see the MCP section below. The promoter shares the link and the password by hand, and /gig/<slug>
answers an ungated visitor with an empty ticket list and no venue. An anonymous JSON read
that answered with tiers, prices and skus would disclose what that page withholds.
List and search events
GET https://gotobeat.com/api/events?search=<text>&cursor=<opaque>&limit=<n>
Returns { events, cursor, hasMore }, with search included for a search request. The default
page size is 12. Pass the returned cursor to fetch the next page.
Without search, results are ordered by event date. With search, direct keyword matches are
paginated. When there are no direct matches, fallbackType identifies the result:
fallbackType |
Response |
|---|---|
artist_events |
events contains upcoming shows for matched artists. |
artist_no_events |
events is empty; matchedArtists contains name, slug, image and genres[]; genreEvents contains up to 12 related shows. |
top_events |
events contains shows ranked by remaining tickets on sale. |
Fallback responses set cursor: null and hasMore: false.
One event
GET https://gotobeat.com/api/events/<slug>
Returns { event, how_to_buy }. event uses the fields above. Each tier with a ticket type
id includes a sku. how_to_buy describes the checkout endpoint and required item identifiers.
Keyword search
GET https://gotobeat.com/api/search/event/<keyword>
Returns a bare array of public event objects. An empty keyword or a failed lookup returns []
with status 200.
Artist and venue pickers
GET https://gotobeat.com/api/artists/search?keyword=<text>
GET https://gotobeat.com/api/venues/search?keyword=<text>
Successful requests return { success: true, data }. Artist rows contain { id, label };
keywords shorter than two characters return an empty array. Venue rows contain
{ id, label, venueId, venueName, city?, country?, hiringOptionsCount }; an empty keyword
returns an empty array. Search failures return HTTP 500 with { success: false, error }.
Structured data on a gig page
Gig pages at https://gotobeat.com/gig/<slug>/ embed schema.org MusicEvent JSON-LD.
Fields depend on the event data and visibility settings:
name, startDate, endDate, doorTime, eventStatus, location (a Place with a
PostalAddress), performer (a PerformingGroup with url and sameAs), image,
typicalAgeRange, isAccessibleForFree, and one Offer per ticket tier with url, sku,
name, price, priceCurrency, availability and validFrom.
Offer fields follow these rules:
Offer.skuis the same<eventId>:<ticketTypeId>string the API returns. Split on the first colon to preserve ticket type ids containing colons.availabilityuses thehttps://schema.org/prefix. The JSON-LD emitsInStockorSoldOutonly, and it reads a tier awaiting its release date asSoldOut. The HTTP API reports that tier asPreSale, because a JSON reader can act on the distinction and a crawler cannot.- One rule decides the answer for every surface. It reads the manual sold-out flag, the release window and a bundle-aware capacity: a tier that shares an exhausted pool with a bundle reads sold out even while its own counters say otherwise. The JSON-LD, the HTTP API, the product feed and the MCP tools therefore agree about what is buyable. Checkout validates current inventory again at reservation time.
- A cancelled event reads
eventStatus: EventCancelledand every offerSoldOut. - A past event keeps
EventScheduled(schema.org has no past state), every offer readsSoldOut, and no offer carries asku. It stays online as a historical record. - An event that sells through another ticketer carries one
Offerwhoseurlis that seller's page and nosku.
Ticket checkout
ACP checkout lets an approved partner create and pay for ticket orders. Obtain a Bearer credential and a signing secret from Gotobeat before integrating.
Endpoints
| Request | Behaviour |
|---|---|
POST /api/checkout_sessions |
Create. items[] is the only required field; buyer and fulfillment_address are optional. items[].id is a sku, quantity a positive integer. The schema is strict, so any other field — currency included — is a 400. Returns 201. |
GET /api/checkout_sessions/:id |
Read the session. |
POST /api/checkout_sessions/:id |
Update items, buyer, fulfillment_address or fulfillment_option_id. An empty body is a 400. |
POST /api/checkout_sessions/:id/complete |
Pay. payment_data carries provider: "stripe", a delegated token, and an optional billing_address. The schema is strict, so any other field is a 400. Produces the order. |
POST /api/checkout_sessions/:id/cancel |
Cancel the session and release any reserved tickets. |
Session lifecycle
A new ticket session starts in ready_for_payment with digital delivery selected. The status
schema also includes not_ready_for_payment, completed and canceled. Supply the buyer's
email before completing payment; a missing email returns buyer_email_required.
line_items contains the priced tiers and totals contains amounts in minor currency units.
A completed session includes order.permalink_url, pointing to /orders/<checkoutId>.
An info entry in messages[] says the tickets are still being issued. Do not branch on a
code there: an info message is projected as { type, content_type, content } and carries none.
The server calculates prices from the current ticket tiers, and a line item carries id and
quantity only — a submitted unit_amount is refused by the strict schema, not repriced.
Checkout accepts paid tickets
for one upcoming, published event per cart. Password-gated events require the browser checkout.
Inventory is reserved during complete.
Idempotency
Use a unique Idempotency-Key of up to 255 characters for each create, update, complete or
cancel operation. Reuse that key when retrying the same operation. Cached successful responses
are replayed for one hour by default; a changed operation needs a new key.
Signing
Production requires signed checkout requests. AGENTIC_CHECKOUT_VERIFY_SIGNATURE turns the
guard on, and staging leaves it off so an integration can be tested unsigned. Send every one
of these headers on a checkout request:
| Header | Content |
|---|---|
Authorization |
Bearer <your credential>. A missing or wrong credential is a 401. |
Signature |
HMAC-SHA256 of the raw request body with your signing secret, base64, in the sig1=:<base64>: form. A bare base64 value is accepted. |
Timestamp |
An RFC 3339 date-time. It is required, not optional. |
Request-Id |
Any id you choose. It comes back on the response Request-Id header, so you can pair the two. |
API-Version |
2025-09-12. Another value is a 400. |
Accept-Language, User-Agent |
Required on every call. |
Content-Type |
application/json on every write. Another type is a 415. |
Idempotency-Key |
Required on a write. See Idempotency above. |
Sign the exact body bytes sent to the server; a GET request signs the empty string. A digest
that is not canonical base64 is a 400, and it never reaches the comparison. A Timestamp more
than five minutes from the server clock is a 401.
The credential and the signing secret are separate values, and a request needs both. Leaking the outbound webhook secret therefore cannot authenticate a checkout request.
One credential signs the surface today. There is no keyid, and no per-partner registry: a
second partner would add a key-id-to-secret map behind the same headers, and no caller would
change.
Checkout applies a best-effort limit of 30 requests per minute per resolved client IP on each warm instance. Back off after HTTP 429.
Checkout errors
Every checkout failure answers with ONE shape, whatever raised it:
{ "type": "invalid_request", "code": "invalid_line_item", "message": "…", "param": "$.items[0].id" }
type classes the STATUS, not the fix: every 4xx reads invalid_request apart from
request_not_idempotent, a 503 reads service_unavailable, and any other 5xx reads
processing_error. Retriability therefore lives in code, not in type — the three codes
marked Retriable below arrive as invalid_request like every other 4xx, and every 5xx is
worth another attempt.
code is the stable string to branch on. message carries the prose — there is no error
key and no details key. param is a JSON path into your request body, and it appears only
when a field caused the refusal. A 500 answers internal_error with a deliberately opaque
message.
| Code | Status | Cause |
|---|---|---|
invalid_request |
400, 401 or 415 | The body, a header or a field failed validation, and param names the field when one did. It is the default code for an uncoded refusal, so a bad Bearer, a bad Timestamp and a wrong Content-Type all carry it. |
invalid_line_item |
400 | The item id is malformed or the ticket tier is missing. |
mixed_events |
400 | The cart includes multiple events. |
event_not_found |
404 | The event id was not found. |
not_found |
404 | No checkout session has that id. The most common 404. |
checkout_error |
503 | Checkout initialisation failed on our side. Retriable. |
internal_error |
500 | The opaque fallback. Retriable. |
event_not_available |
400 | The event is not published, already happened, or needs a password. |
free_event_not_supported |
400 | The cart total is zero. |
checkout_unavailable |
409 | Checkout initialisation failed, for example because a tier sold out. |
invalid_state |
400, 405 or 409 | The session is in the wrong status for the call. Cancelling an already-cancelled session answers 405. |
session_not_payable |
400 | The session has no payable order. |
buyer_email_required |
400 | The buyer email is missing at completion. |
payment_declined |
402 | Payment failed and the reservation was released. |
payment_pending |
409 | Payment or fulfilment is unresolved. Poll the session. |
order_not_amendable |
409 | The order moved past checkout. |
order_already_placed |
405 or 409 | The order is paid. Use the support flow for refunds. |
checkout_in_progress |
409 | A concurrent complete moved the order on under a cancel. Re-read the session. Retriable. |
request_in_progress |
409 | Another request holds the same Idempotency-Key and is still running. Retriable. |
request_not_idempotent |
409 | The same Idempotency-Key arrived with a different body. Use a new key. |
session_not_amendable |
409 | The session refuses every amendment. |
invalid_fulfillment_option |
400 | The fulfillment_option_id names no option on the session. |
rate_limit_exceeded |
429 | Back off, then retry. |
payload_too_large |
413 | The body exceeds the accepted size. |
idempotency_unavailable |
503 | The idempotency store could not answer. Retriable. |
order_update_failed |
500 | The order update failed. Retry with the same idempotency key. |
order_unavailable |
503 | The order could not be read. Retry the call. |
payment_setup_failed |
500 | The payment could not be set up. |
MCP
The MCP server is available at https://mcp.gotobeat.com.
Discovery
The server publishes RFC 9728 protected-resource metadata at
https://mcp.gotobeat.com/.well-known/oauth-protected-resource. A compliant client reads it to
find the authorisation server, the supported bearer method (header) and the scopes
(mcp:tools:read, mcp:tools). The server supports RFC 7591 dynamic client registration at
/oauth/register and S256 PKCE, allowing compatible MCP clients to register during connection setup.
Anonymous tools
POST https://mcp.gotobeat.com/public accepts JSON-RPC with no token. It exposes three
read-only tools.
| Tool | Argument | Returns |
|---|---|---|
public_search_events |
keyword |
{ events[] }: upcoming published gigs whose title, artist, venue or city matches |
public_get_event |
idOrSlug |
a gig card plus description, tiers[] (sku, name, price, currency, availability) and how_to_buy instructions |
public_list_upcoming_events |
limit (default 12, max 50), cursor |
{ events[], cursor }, soonest first |
A gig card is { title, date, venue: { name, city }, slug, url, priceFrom, priceTo, currency }.
MCP results contain JSON encoded in content[].text. Card prices and tier prices are numbers.
Every tool refuses a password-gated event, and each gate reads the stored row before the
public projection strips the password. public_search_events and public_list_upcoming_events
also hide a finished show. public_get_event does not: asked for one by id or slug, it answers
with the record and reads every tier SoldOut, which is the honest answer to a question about
a gig that already happened. MCP uses its own projection, narrower than the HTTP API's, and it
returns the event description unprocessed where the HTTP API strips markup from it.
An unknown event or a status rejected by the detail tool returns an MCP error with
Event not found. Only a published event is public.
The anonymous route allows 60 requests per 60 seconds per resolved source IP on each warm
instance. Requests routed through the same CloudFront edge can share a budget. A 429 includes
Retry-After; wait for that interval before retrying.
Authenticated tools
Add https://mcp.gotobeat.com to a compatible MCP client and sign in with your Gotobeat account.
OAuth registration and consent use the discovery endpoints above. The authenticated tool set
is selected by the account's role: promoters receive tools scoped to their own data, while
staff accounts receive an operational tool set. Query tools/list after authentication to
inspect the available tools and argument schemas.