Grofomo API
Browse the docs

Examples use a fictional festival, summer-fest. Sign in at events.grofomo.com and they switch to your own events.

Ticket availability

GET/v1/events/{slug}/tickets

The ticket types and release tiers a visitor is allowed to see, with live availability.

Visibility is resolved server-side, so this only ever returns what the public should see: time-gated tiers appear when they open, and hidden sold-out tiers stay hidden.

Read soldOut rather than inferring from available. available is null both when a tier is unlimited and when the organiser has chosen not to publish counts — soldOut is always authoritative.

Selling tickets from your own site is not part of this API yet. Use this feed to show prices and availability, and link to the Grofomo ticket page to sell; an embeddable checkout is coming.

Send ?surface=webOmit it and the request is treated as coming from the mobile app, which is gated on a different visibility setting — so a perfectly live event can return 404 not_found for no visible reason.

Request

curl -sS \
  -H 'X-Grofomo-Key: pk_live_YOUR_PUBLISHABLE_KEY' \
  'https://api.grofomo.com/v1/events/summer-fest/tickets?surface=web'

Path parameters

slugstringrequired
The event's URL slug. Globally unique, and fixed once the event is created. Example: `summer-fest`.

Query parameters

surface'web'required
Must be `web` for a website. Omit it and you get the mobile-app surface, which is gated on a different visibility setting — so a perfectly live event can return 404. Example: `web`.
promostring
A live unlock code, to reveal promo-gated ticket types.

Response

200 OK
{
  "apiVersion": 1,
  "data": {
    "categories": [
      {
        "id": "6f1c8e2a-4b3d-4c9e-9f21-8a7d5e0b1c35",
        "title": "Weekend",
        "description": "All three days, with camping."
      }
    ],
    "types": [
      {
        "id": "b2d4a6f8-1c3e-4a5b-8d7f-2e9c0a1b3d46",
        "categoryId": "6f1c8e2a-4b3d-4c9e-9f21-8a7d5e0b1c35",
        "title": "Weekend camping ticket",
        "description": "Entry Friday to Sunday, plus a camping pitch from Friday 10:00.",
        "descriptionDoc": {
          "type": "doc",
          "content": [
            {
              "type": "paragraph",
              "content": [
                {
                  "type": "text",
                  "text": "Entry Friday to Sunday, plus a camping pitch from Friday 10:00."
                }
              ]
            }
          ]
        },
        "ticketsPerGroup": 1,
        "minPerOrder": 1,
        "maxPerOrder": 6,
        "isHighlighted": true,
        "highlightMessage": "Most popular",
        "paymentPlan": {
          "depositBps": 2500,
          "installments": 3
        },
        "releases": [
          {
            "id": "3e5a7c9b-0d2f-4e6a-9b8c-1d3f5a7e9c03",
            "label": "Tier 1",
            "pricePennies": 9500,
            "currency": "GBP",
            "available": null,
            "capacity": null,
            "closesAt": null,
            "opensAt": null,
            "upcoming": false,
            "soldOut": true,
            "closed": false
          },
          {
            "id": "9c7e5a3b-1f0d-4b2e-8a6c-4d2f0b8e6a14",
            "label": "Tier 2",
            "pricePennies": 11000,
            "currency": "GBP",
            "available": 340,
            "capacity": 1500,
            "closesAt": "2026-07-10T22:59:00.000Z",
            "opensAt": null,
            "upcoming": false,
            "soldOut": false,
            "closed": false
          },
          {
            "id": "47a9e1c3-5b8d-4f20-9c6e-3a1b7d5f9e03",
            "label": "Final release",
            "pricePennies": 12500,
            "currency": "GBP",
            "available": null,
            "capacity": null,
            "closesAt": null,
            "opensAt": "2026-07-11T09:00:00.000Z",
            "upcoming": true,
            "soldOut": false,
            "closed": false
          }
        ]
      }
    ],
    "quantityDiscounts": [
      {
        "id": "d1f3b5a7-9c2e-4d80-8b6f-5a3c1e7d9b05",
        "name": "Group of four",
        "kind": "tiered_percent",
        "applicableReleaseIds": [
          "9c7e5a3b-1f0d-4b2e-8a6c-4d2f0b8e6a14"
        ],
        "tiers": [
          {
            "minQuantity": 4,
            "value": 1000
          }
        ],
        "combinableWithPromo": false
      }
    ]
  }
}
categoriesobject[]
Optional grouping for the types below.
categories[].iduuid
Referenced by `categoryId` on a ticket type.
categories[].titlestring
Section heading, e.g. "Weekend".
categories[].descriptionstring | null
typesobject[]
Only what this caller is allowed to see. Hidden and promo-gated types are filtered server-side.
types[].iduuid
types[].categoryIduuid | null
Groups this type under a category. Null when ungrouped.
types[].titlestring
types[].descriptionstring | null
Plain-text description, for clients that render no rich text.
types[].descriptionDocobject | null
The same description as a ProseMirror document. Render this when present, otherwise `description`.
types[].descriptionDoc.type"doc"
types[].descriptionDoc.contentobject | object | object | object | object | object[]may be absent
types[].ticketsPerGroupnumber
Admissions issued per unit bought. 1 is a single ticket; higher means a table or group ticket.
types[].minPerOrdernumber
Fewest units one order may contain.
types[].maxPerOrdernumber
Most units one order may contain.
types[].isHighlightedboolean
The organiser wants this type visually promoted.
types[].highlightMessagestring | null
Badge text for a highlighted type, e.g. "Most popular".
types[].paymentPlanobject | null
Set when this type can be bought on a payment plan. Null otherwise.
types[].paymentPlan.depositBpsnumber
Deposit as basis points of the total, so 2500 is 25%.
types[].paymentPlan.installmentsnumber
Number of payments after the deposit.
types[].releasesobject[]
Price tiers, in sale order. Only tiers this caller is allowed to see are included.
types[].releases[].iduuid
types[].releases[].labelstring
Release name, e.g. "Tier 2" or "Final release".
types[].releases[].pricePenniesnumber
Price in minor units, so 9500 is £95.00.
types[].releases[].currencystring
ISO 4217 code, e.g. "GBP".
types[].releases[].availablenumber | null
Tickets left. Null means EITHER unlimited OR the organiser has chosen not to publish counts — so this can never gate a buy button on its own. Use `soldOut`.
types[].releases[].capacitynumber | nullmay be absent
Total allocation, hidden under the same rule as `available`. With it you can show percent sold.
types[].releases[].closesAtstring | nullmay be absent
When this release stops selling. Null means no cut-off.
types[].releases[].opensAtstring | nullmay be absent
When this release starts selling. Set alongside `upcoming`, and the target for a countdown.
types[].releases[].upcomingbooleanmay be absent
A teaser: listed but not yet purchasable. Takes precedence over `soldOut` and `closed`, which are both false while it holds.
types[].releases[].soldOutboolean
Authoritative. Gate your buy button on this, never on `available`.
types[].releases[].closedboolean
The sale window has ended. Still shown, no longer purchasable.
quantityDiscountsobject[]may be absent
Automatic discounts, in enough detail to advertise ("Buy 4+ and save 10%") and preview locally. The server stays authoritative at checkout.
quantityDiscounts[].iduuid
quantityDiscounts[].namestring
Organiser-facing name, e.g. "Group of four".
quantityDiscounts[].kind"tiered_percent" | "tiered_fixed" | "buy_n_get_m" | "bundle_price"
Which of the fields below apply, and how `tiers[].value` should be read.
quantityDiscounts[].applicableReleaseIdsuuid[]
Releases this rule applies to. Empty means the whole order.
quantityDiscounts[].tiersobject[]may be absent
Present for the two tiered kinds.
quantityDiscounts[].tiers[].minQuantitynumber
Quantity at which this tier starts applying.
quantityDiscounts[].tiers[].valuenumber
Basis points off for `tiered_percent` (1000 = 10%), or pennies off per ticket for `tiered_fixed`.
quantityDiscounts[].buyQuantitynumbermay be absent
The "buy N" of a `buy_n_get_m` rule.
quantityDiscounts[].freeQuantitynumbermay be absent
The "get M free" of a `buy_n_get_m` rule.
quantityDiscounts[].bundleSizenumbermay be absent
Tickets per bundle, for `bundle_price`.
quantityDiscounts[].bundlePricePenniesnumbermay be absent
Price of one whole bundle, for `bundle_price`.
quantityDiscounts[].combinableWithPromoboolean
Whether a promo code can stack on top of this discount.
Generated from EventTicketsResponse

Errors

unauthorizedHTTP 401
No key was sent and the organiser requires one — or the key is invalid, revoked, belongs to another organisation, or is scoped to a different event.
origin_not_allowedHTTP 403
Your site's domain is not on the organiser's allowed list. Browser calls only: a server sends no Origin header and is unaffected.
not_foundHTTP 404
No event with that slug is published on this surface. Check the slug, and check you sent `?surface=web`.

Notes

  • Rate limited to roughly 60 requests per minute per caller. Guidance, not a contract — see caching and rate limits.