CaptainTicket API

Read the live state of your ticket shop

A small, token-authenticated, read-only REST API: your events with live availability, ticket shop URLs and active payment methods. Built for the website, kiosk or LED wall that needs to know whether tickets are still available — without scraping.

Read-only Bearer tokens 600 req/hourv1

Overview

Every endpoint answers with data your ticket shop already publishes: the same events, prices and availability a visitor sees on /t/<slug>, and the payment methods your shop is configured to offer. There is no buyer data, no orders and no personal information in this API.

The base URL is your CaptainTicket site (https://captainticket.nl in production). All endpoints are under /api/v1 and respond with JSON.

Quickstart

  1. Sign in to the dashboard and open Instellingen → API-tokens (admin role required, and your shop must be fully set up).
  2. Create a token, give it a name you will recognize later, and copy the ctk_… value — it is shown exactly once.
  3. Call an endpoint with the token in the Authorization header:
curl -H "Authorization: Bearer ctk_YOUR_TOKEN" \
  https://captainticket.nl/api/v1/events

A shop with one upcoming festival will get something like:

{
  "events": [
    {
      "id": 482,
      "name": "Zomersessie 2026",
      "startsAt": "2026-08-15T18:00:00.000Z",
      "endsAt": "2026-08-15T23:00:00.000Z",
      "onSale": true,
      "soldOut": false,
      "url": "https://captainticket.nl/t/vereniging-voorbeeld/e/482",
      "location": { "name": "Sporthal De Sluis", "address": "Sluisstraat 1, Voorburg" }
    }
  ],
  "total": 1,
  "limit": 50,
  "offset": 0
}

Authentication

All endpoints require an API token. Send it as a bearer token:

Authorization: Bearer ctk_YOUR_TOKEN

A token belongs to exactly one shop and grants read access to that shop's public data only — it cannot name, list or read another shop. Tokens can carry an expiry date, and can be revoked at any time from the dashboard; both take effect on the very next request.

Missing, invalid, expired and revoked tokens all answer the same 401: the API never confirms whether a token ever existed. Treat the token like a password; it is only shown once at creation.

Rate limits

Each token may make 600 requests per rolling hour — about ten per minute sustained, which is far more than a display or website needs. When the limit is exceeded the API answers 429 with a Retry-After: 60 header.

Polling sold-out status once a minute per event is well within the limit. If you need more, contact us before working around it.

Errors

Every failure has the same JSON shape: a stable error code to branch on and a human-readable message:

{ "error": "not_found", "message": "Event not found." }
ParameterDescriptionDefault
400 invalid_requestMalformed parameters (for example a non-numeric event id).—
401 unauthorizedMissing, invalid, expired or revoked token.—
404 not_foundThe resource does not exist — or belongs to another shop; the API does not distinguish.—
429 rate_limitedToo many requests with this token; see Retry-After.—

Conventions

  • Timestamps are ISO 8601 strings. Event times are Europe/Amsterdam — that is the time at the door, regardless of your server's timezone.
  • Money is integer cents: priceCents: 1500 is €15.00. Never parse it as a float.
  • Null means unlimited: a null capacity or availability is a tier without a ceiling.
  • URLs are absolute. Dutch (nl) is each shop's canonical locale; a shop may enable more, and per-locale URLs are included where relevant.
  • Caching: responses are no-store — availability is live. Do not cache payloads longer than your polling interval.

The shop

GET/api/v1/shop#

Your shop's public identity: name, slug, enabled locales and the absolute ticket shop URL per locale. testMode is true while the platform has your shop in Pay.nl test mode — worth surfacing so nobody is confused about money not moving.

Example response

{
  "id": "259",
  "slug": "vereniging-voorbeeld",
  "name": "Vereniging Voorbeeld",
  "urls": {
    "nl": "https://captainticket.nl/t/vereniging-voorbeeld",
    "en": "https://captainticket.nl/t/vereniging-voorbeeld/en"
  },
  "locales": ["nl", "en"],
  "testMode": false
}

Events overview

GET/api/v1/events#

The shop's public events — visible, not archived, not an unpublished draft — each with a live soldOut flag computed the same moment the public shop page computes it (paid tickets plus unexpired reservations count as taken).

ParameterDescriptionDefault
limitPage size, 1–100.50
offsetPage offset; page through until offset + length reaches total.0

Events are ordered by startsAt (undated events last). See the quickstart for a full example response.

Event details

GET/api/v1/events/{eventId}#

One event in full: description (HTML), sale window, location, per-tier prices and live availability, time slots, and the public URL per enabled locale. The id comes from the list endpoint.

Example response (abridged)

{
  "id": 482,
  "name": "Zomersessie 2026",
  "description": "<p>De jaarlijkse zomersessie…</p>",
  "startsAt": "2026-08-15T18:00:00.000Z",
  "endsAt": "2026-08-15T23:00:00.000Z",
  "saleStartsAt": "2026-06-01T00:00:00.000Z",
  "onSale": true,
  "soldOut": false,
  "queue": false,
  "url": "https://captainticket.nl/t/vereniging-voorbeeld/e/482",
  "urls": { "nl": "https://captainticket.nl/t/vereniging-voorbeeld/e/482" },
  "location": {
    "name": "Sporthal De Sluis",
    "address": "Sluisstraat 1, Voorburg",
    "latitude": 52.077,
    "longitude": 4.359
  },
  "presale": { "enabled": false, "startsAt": null },
  "tiers": [
    {
      "id": 1201,
      "name": "Normaal",
      "description": null,
      "priceCents": 1500,
      "vatRate": "low",
      "capacity": 200,
      "available": 63,
      "soldOut": false,
      "onSale": true,
      "minimumOrderCount": 1,
      "maximumOrderCount": 6,
      "increment": 1,
      "timeSlots": []
    }
  ],
  "timeSlots": []
}

vatRate is a semantic category (free, zero, low = 9%, high = 21%). available honors shared time-slot pools — it is the number a buyer can actually still reserve, which can be lower than capacity.

Payment methods

GET/api/v1/payment-methods#

The payment methods your shop is configured to offer: your allowlist intersected with the methods CaptainTicket has enabled platform-wide (iDEAL only until you pick more). Vouchers (gift cards) are typed separately so you can group them in a UI.

Example response

{
  "methods": [
    { "id": 1, "name": "iDEAL", "type": "standard" },
    { "id": 706, "name": "Podium Cadeaukaart", "type": "voucher" }
  ]
}

This reads your configuration — it makes no live call to the payment provider. The exact set a buyer can pick is re-resolved at checkout, so a method can disappear from a real payment if the provider stops offering it. Use this endpoint for display, not for payment validation.

OpenAPI & tooling

The complete contract is published as an OpenAPI 3.1 document, generated from the same schemas that shape the responses — it cannot drift from the implementation:

openapi.json

Point your client generator, Postman or CI contract test at it.

Download

AI agents: this documentation, the spec and /llms.txt describe the same API.

Changelog

  • v1 (2026-10-03) — read-only API with /shop, /events, /events/{eventId} and /payment-methods; API tokens with expiry and revocation.

Write operations and webhooks are planned but not available. Questions or missing data for your integration? info@captainticket.nl.