This page is written for machines, and for the people who write them. It describes every public surface an autonomous agent needs to find a gig on Gotobeat, read its ticket tiers, and buy them.
Gotobeat is a live-music ticketing platform. We list concerts across the UK and Europe and sell the tickets ourselves — no resale, no markup. There are five ways in, and you can mix them freely:
Three machine-readable entry points sit at the root of the domain:
Every endpoint on this page is explicitly allowed in robots.txt. Everything else under /api/ is internal — please respect it. Page URLs carry a trailing slash; a URL without one answers with a 301 to the slashed form.
Every event page at /gig/<slug>/ embeds a schema.org MusicEvent JSON-LD block, rendered as <script type="application/ld+json"> in the body. It carries the venue, the start and end time, the performers, and one Offer per ticket tier.
Each Offer looks like this:
{
"@type": "Offer",
"url": "https://gotobeat.com/gig/<slug>/",
"sku": "<eventId>:<ticketTypeId>",
"name": "Early Bird",
"price": "20",
"priceCurrency": "GBP",
"availability": "https://schema.org/InStock",
"validFrom": "2026-01-15T00:00:00.000Z"
}Offer.sku is the id you pass as items[].id when you create a checkout session. It is the event id and the ticket type id joined by a single colon. An event id never contains a colon, so a parser splits on the first one. This is the only identifier you need to carry from a page read to a purchase.
Availability semantics:
https://schema.org/InStock — the tier is on sale right now.https://schema.org/SoldOut — the tier is sold out, unreleased, or the event has already happened.eventStatus: EventCancelled and every offer flips to SoldOut. A past event keeps eventStatus: EventScheduled — schema.org has no "past" status — and its offers read SoldOut.Prices are decimal strings paired with priceCurrency (GBP or EUR). An event that hides its date or its venue omits those fields entirely rather than faking them.
startDate and endDate carry the venue-local UTC offset. A gig that runs past midnight ends on the following day, so its endDate is never before its startDate.
GET /api/events/list.csvAn RFC 4180 CSV in the Google Merchant column shape. One row is one ticket tier, not one event: a two-tier gig produces two rows that share an item_group_id. Use it to sweep the whole catalogue in one request instead of crawling every gig page.
| Column | What it holds |
|---|---|
id | The <eventId>:<ticketTypeId> sku, ready to post as an ACP items[].id. |
item_group_id | The event id. Every tier of one gig shares it. |
title | Event — Tier. |
description | The event description as plain text. HTML tags are gone and HTML entities are decoded. |
link | The canonical gig URL, with its trailing slash. It never redirects. |
price / currency | 12.50 GBP — the tier price, before the booking fee. The fee appears in the checkout session totals. |
availability | in stock or out of stock. |
start_date / end_date | RFC 3339 with the venue-local UTC offset. An overnight gig ends on the following date. |
availability applies the same manual sold-out flag, bundle-aware capacity and release window as the JSON-LD on the gig page and as the MCP tools, therefore all three surfaces agree. A past event produces no row. A legacy tier that carries no ticket type id produces no row either, because it has no stable sku — a broken id is worse for an agent than an absent one.
The response is cached for five minutes at the shared cache. It is covered by the Allow: /api/events rule in robots.txt.
Four read-only GET endpoints. They need no authentication. Note that they do not share one envelope shape — the two search helpers below were built for the site's own autocomplete and are documented here exactly as they behave.
GET /api/eventsThe main listing. Query parameters:
search — free text. Falls back through artist matches and genre matches before it gives up.cursor — opaque. Pass back the cursor value from the previous response. Do not construct one.limit — page size. Defaults to 12.The response is a bare object with no success/data envelope:
{
"events": [ /* sanitised public event objects */ ],
"cursor": "MTI=" | null,
"hasMore": true | false,
"search": "oasis", // echoed back when you sent one
"fallbackType": "artist_events" | "artist_no_events" | "top_events"
}fallbackType appears only when the direct search found nothing and we widened it. When it is artist_no_events the matched artists arrive in matchedArtists and the suggestions in genreEvents, while events is empty. A fallback response is never paginated: cursor is null and hasMore is false.
GET /api/search/event/:keywordKeyword event lookup. Returns a bare array of sanitised event objects — no wrapper at all. An empty keyword or a failed lookup returns [], never an error status.
GET /api/artists/search?keyword=Returns { "success": true, "data": [{ "id", "label" }] }. A keyword shorter than two characters returns an empty data array.
GET /api/venues/search?keyword=Returns { "success": true, "data": [...] }. Each row carries id, label, venueId, venueName, city, country and hiringOptionsCount.
Buying is an Agentic Commerce Protocol flow at API-Version: 2025-09-12. You create a session, refine it until it is payable, then complete it with a delegated payment token. All four mutating calls require an Idempotency-Key header.
| Method & path | Purpose | Idempotency-Key |
|---|---|---|
POST /api/checkout_sessions | Create a session from a list of items. Answers 201. | Required |
GET /api/checkout_sessions/:id | Read the current session state. | — |
POST /api/checkout_sessions/:id | Update items, buyer, fulfilment address or fulfilment option. | Required |
POST /api/checkout_sessions/:id/complete | Pay with a delegated payment token. Produces the order. | Required |
POST /api/checkout_sessions/:id/cancel | Abandon the session and release the held seats. The body is ignored. | Required |
Every call carries all of these. A missing one is a 400, and a wrong one a 401.
Authorization: Bearer <credential> — the credential we issue at onboarding.API-Version: 2025-09-12 — the only accepted value.Accept-Language, User-Agent, Request-Id.Timestamp — an RFC 3339 date-time, for example 2026-08-27T12:00:00Z. It must be within 300 seconds of our clock. A value that is not an RFC 3339 date-time is a 400, and a stale one a 401.Signature — see below.Content-Type: application/json and Idempotency-Key.Every response echoes Request-Id and Idempotency-Key, sets API-Version: 2025-09-12, and sets Cache-Control: no-store. A request body over 64 KB is a 413. One caller gets 30 requests in a 60-second window, counted across all five operations; over that is a 429 and no idempotency key is consumed.
Each entry in items[] needs an id and a positive integer quantity, and nothing else. The id is the <eventId>:<ticketTypeId>sku you read from the JSON-LD, from the product feed or from the MCP tools.
We price the cart. The schemas are closed, so a unit_amount, a currency, a name, an image_url, a metadata bag or a self-declared fulfillment_options array is a 400 — not a value we quietly ignore. The price, the currency and the tier title come from the event. A session accepts items, an optional buyer (name and email required together, optional E.164 phone_number) and an optional fulfillment_address. Update also accepts fulfillment_option_id, which must name the option the session already offers.
A ticket is a digital good, so the one digital fulfilment option is selected on creation and the session reports ready_for_payment at once. Every item of one session must belong to the same event.
Complete takes payment_data: { provider: "stripe", token, billing_address? }. The field is token; there is no shared_payment_token.
Send a unique Idempotency-Key on every mutating request, 255 characters or fewer. The claim is scoped to the HTTP method, the path and that key, and it is fingerprinted with a SHA-256 of the raw request body. The key is claimed atomically before we touch an order or a payment.
request_not_idempotent.request_in_progress.Requests are signed with an HMAC-SHA256 over the raw body. Send the base64 digest in the Signature header, in the sig1=:<base64>: form; a bare base64 value and a base64url value are both accepted. A GET signs the empty string. Only canonical base64 is accepted — a truncated or wrongly padded digest is a 400, never a comparison. Freshness comes from the Timestamp header above. Talk to us to get a signing secret.
Every checkout failure answers with one shape:
{
"type": "invalid_request" | "request_not_idempotent" | "processing_error" | "service_unavailable",
"code": "invalid_line_item",
"message": "Item id \"oasis\" is malformed. Use the \"<eventId>:<ticketTypeId>\" sku from the gig page.",
"param": "$.items[0].id"
}type says who must act: invalid_request and request_not_idempotent are yours to fix, processing_error and service_unavailable are ours — retry those. code is a stable string you may branch on, and param is a JSON path into your request body. A 500 message is deliberately opaque.
A session reports its own readiness in status:
not_ready_for_payment — something is still missing. Read messages[] to learn what.ready_for_payment — call /complete.completed — paid. The order carries a permalink_url of the form /orders/<checkoutId>, which is the buyer-facing confirmation page.canceled — done, nothing was charged.A missing session answers 404, a malformed body 400, and a bad signature or a stale timestamp 401. Updating a completed or canceled session answers 409. Cancelling a session that is already terminal answers 405 — an exact replay of the original Idempotency-Key still answers the cached 200, and a paid order needs a refund rather than a cancellation.
A completed session carries payment_provider.provider, nested line_items[].item, totals[] with a display_text and a minor-unit amount, the digital fulfilment option, and the order. It carries no internal id, no Stripe PaymentIntent id, no client secret, no timestamp and no metadata bag.
Our Model Context Protocol server lives at https://mcp.gotobeat.com. It speaks OAuth 2.1, and it publishes RFC 9728 protected-resource metadata at /.well-known/oauth-protected-resource. A compliant client reads that document to discover the authorisation server and the supported scopes, so you should not need to hard-code anything.
A public tier at https://mcp.gotobeat.com/public needs no authentication and exposes read-only event search. It ships in this same release. The authenticated surface is much wider, and it is scoped to the promoter who owns the token.
If something on this page is wrong, or you need a signing secret, write to us. We would rather hear from your agent than debug it from the access logs — and yes, we do take requests.