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.

Order status

GET/v1/events/{slug}/tickets/orders/{checkoutToken}

Everything a confirmation page needs after the purchase it just made: where the order is, its lines and totals read back from the server, any personal promo codes the buyer earned on the organiser's other events, and the buyer's own Ambassador code and reward ladder where the organiser runs one.

After a card payment the order stays reserved until the payment is confirmed server-side, usually within a minute. Poll every few seconds until orderStatus reads completed, then tell the buyer their tickets are on their way; they arrive by email from Grofomo. Give up after a couple of minutes with a softer message: the confirmation email still arrives, and the buyer's card has not been charged twice.

Print the receipt from order rather than from what your page held in memory: it survives a payment redirect, and shortId is the reference printed on the tickets.

codes is empty until the order is completed, and stays empty when the cart earned nothing. When a code is present, offer a link to the target event's ticket page with ?promo=<code> so it is pre-filled; the same code is in the confirmation email. ambassador is null until the order completes, and stays null when the organiser has no programme or has automatic enrolment off.

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/orders/YzNhMWU1ZDctOWIyZi00ZTZhLThjMGQtMmY0YjZhOGMwZTEyOjE3ODk2NDM2MDAwMDA6dGt0LWNoZWNrb3V0.ZXhhbXBsZS1zaWduYXR1cmU?surface=web'

Path parameters

slugstringrequired
The event's URL slug. Globally unique. It can change if the organiser renames the event, but an old slug keeps reaching the same event, so a stored one never breaks. Example: `summer-fest`.
checkoutTokenstringrequired
The order's `checkoutToken` from the reserve response. It authorises this checkout only and expires a day after the reserve; keep it out of logs and analytics. Example: `YzNhMWU1ZDctOWIyZi00ZTZhLThjMGQtMmY0YjZhOGMwZTEyOjE3ODk2NDM2MDAwMDA6dGt0LWNoZWNrb3V0.ZXhhbXBsZS1zaWduYXR1cmU`.

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`.

Response

200 OK
{
  "apiVersion": 1,
  "data": {
    "orderStatus": "completed",
    "codes": [
      {
        "offerId": "e7c5a3b1-2d4f-4a6c-8e0b-3f5d7a9c1e24",
        "name": "Winter warmer",
        "targetEventName": "Winter Fest",
        "targetEventSlug": "winter-fest",
        "discountType": "percentage",
        "discountValue": 1500,
        "applicableReleaseIds": [],
        "code": "SF-WINTER-7K3M",
        "expiresAt": "2026-12-01T00:00:00.000Z"
      }
    ],
    "order": {
      "shortId": "T-3F9A2C",
      "email": "alex@example.com",
      "currency": "GBP",
      "lines": [
        {
          "label": "Weekend camping ticket",
          "quantity": 2,
          "unitPricePennies": 9900,
          "lineTotalPennies": 19800
        }
      ],
      "subtotalPennies": 22000,
      "discountPennies": 2200,
      "quantityDiscountName": null,
      "bookingFeePennies": 1535,
      "totalPennies": 21335,
      "paidTodayPennies": 21335,
      "paymentPlan": null
    },
    "ambassador": {
      "code": "SF-ALEX-9K2Q",
      "shareUrl": "https://grfm.to/e/summer-fest/tickets?aff=SF-ALEX-9K2Q",
      "qualifyingTickets": 0,
      "tiers": [
        {
          "thresholdTickets": 2,
          "tierName": null,
          "title": "5 free drinks",
          "description": null,
          "fulfilmentInstructions": "At the festival bar"
        },
        {
          "thresholdTickets": 4,
          "tierName": "VIP",
          "title": "VIP bar access",
          "description": "All day, for you and a plus one",
          "fulfilmentInstructions": null
        }
      ]
    }
  }
}
orderStatusstring
Where the order is. `reserved` while the hold is live (including after a card payment, until the payment is confirmed server-side, usually within a minute); `completed` once tickets are issued; `expired` when the hold ran out unpaid; `cancelled`, `refunded` or `partially_refunded` after the fact. Poll this after payment until it reads `completed`.
codesobject[]
Personal promo codes this order earned on other events. Empty until the order is `completed`, and empty forever when the cart earned none.
codes[].offerIduuid
The offer the code came from.
codes[].namestring
The organiser's label for the offer.
codes[].targetEventNamestring
The event the code is spent on.
codes[].targetEventSlugstring
Slug of the target event. Link to its ticket page with `?promo=<code>` to pre-fill the code.
codes[].discountType"percentage" | "fixed"
How the code discounts: a percentage of the tier price, or a fixed amount off.
codes[].discountValuenumber
Basis points for percentage (1000 = 10%), minor units for fixed.
codes[].applicableReleaseIdsuuid[]
Release ids on the target event the code discounts. Empty means every tier.
codes[].codestring
The personal promo code. It is also emailed to the buyer.
codes[].expiresAtstring | null
ISO instant the code stops working, or null when it never expires.
orderobjectmay be absent
The order's lines and totals, read back from the server: what a confirmation page should print rather than what it held in memory across a payment redirect.
order.shortIdstring
The human order reference printed on the tickets, e.g. "T-3F9A2C".
order.emailstring | null
order.currencystring
order.linesobject[]
order.lines[].labelstring
The ticket name as sold, e.g. "Adult".
order.lines[].quantitynumber
order.lines[].unitPricePenniesnumber
Price per unit after any automatic discount, before the booking fee.
order.lines[].lineTotalPenniesnumber
order.subtotalPenniesnumber
order.discountPenniesnumber
order.quantityDiscountNamestring | null
order.bookingFeePenniesnumber
order.totalPenniesnumber
order.paidTodayPenniesnumber
order.paymentPlanobject | null
order.paymentPlan.iduuid
The payment plan offer the order is on.
order.paymentPlan.namestring
The organiser's name for the plan, to show the buyer.
order.paymentPlan.depositPenniesnumber
What the payment step charges now: the deposit share of the face value plus the whole booking fee, in minor units.
order.paymentPlan.balancePenniesnumber
The remaining face value, collected over the instalments, in minor units.
order.paymentPlan.payByAtstring
ISO instant the plan must be fully paid by. The last instalment lands on or before it.
order.paymentPlan.installmentsobject[]
The exact schedule the saved card is charged on once the deposit is paid. Show it before the buyer pays.
order.paymentPlan.installments[].seqnumber
1-based position in the schedule.
order.paymentPlan.installments[].amountPenniesnumber
Charged on `dueAt`, in minor units.
order.paymentPlan.installments[].dueAtstring
ISO instant the instalment is taken.
ambassadorobject | nullmay be absent
The buyer's own Ambassador share code and reward ladder, once the order completed and the organiser runs a programme with automatic enrolment. Null otherwise.
ambassador.codestring
ambassador.shareUrlstring
ambassador.qualifyingTicketsnumber
ambassador.tiersobject[]
ambassador.tiers[].thresholdTicketsnumber
Tickets that must sell through the code before this reward is earned.
ambassador.tiers[].tierNamestring | null
ambassador.tiers[].titlestring
What they get, e.g. "5 free drinks".
ambassador.tiers[].descriptionstring | null
ambassador.tiers[].fulfilmentInstructionsstring | null
Generated from OrderCrossEventCodesResponse

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 order for that `checkoutToken` on this event (or the token has expired), or no event with that slug on this surface.

Notes

  • Rate limited to roughly 60 requests per minute per caller. Guidance, not a contract — see caching and rate limits.
  • Cache-Control: no-store