One API. Ours is generated from it too.

Felt’s apps are built from the same OpenAPI document we publish: the TypeScript and Swift clients are generated, not hand-kept. That is the only reliable way to keep a public spec honest, and it means the API you integrate against is the one running every tournament.

83 paths · 122 operations, validated with the same schemas the routes parse with. A casino driving Felt from its own floor system is the adoption path we designed for, not an edge case we tolerate.

Get openapi.json

# every action is a command against the event log
$ curl -X POST \
  https://api.feltpoker.uk/v1/tournaments/{tournament}/commands \
  -H "Authorization: Bearer felt_live_k3…" \
  -H "Content-Type: application/json" \
  -d '{ "type": "entry.bust",
        "data": { "entryId": "01JGA3W8Q4T5V6X7Y8Z9A0B1C2" } }'

# 202 — the log answers with what it appended
{ "accepted": true, "epoch": 41, "seq": 412,
  "eventIds": ["01JGA3WA…"], "replayed": false }

Three principals, one scope set.

Every request is a person, a device, or a key, and nothing pretends otherwise.

People

Passkey-first sessions for organisers and venue managers, with roles scoped to the topology: a brand admin sees the brand, a venue manager sees exactly their venue.

Devices

Floor phones and screens join by QR grant: approved from the director’s device, allow-listed, and revocable instantly, mid-event, one at a time or all at once. No accounts anywhere near the floor.

API keys

felt_live_… and felt_test_…, org-scoped, hashed at rest with a displayed prefix, per-key scopes, rotation without downtime, and a last-used timestamp so dead keys are visible.

Boring on purpose.

The parts of an API that make an integration survivable are the unglamorous ones. They’re written down before the first external integrator, not after.

Versioning Additive changes ride a date header. Breaking changes only at /v2, with twelve months’ notice and Sunset and Deprecation headers on the way out. oasdiff runs in CI and fails the build on an unannounced break.
Webhooks Signed deliveries for tournament and season events, delivered by a durable queue rather than a best-effort loop.
Live commands The same commands our own apps issue — bust-outs, re-entries, clock control — available under a tournament:write scope, rate-limited.
Cinema embed A documented embed so a room with existing display infrastructure renders Felt’s boards on its own hardware. Bring your own screens.
Integrity The event log is hash-chained and verifiable: GET /tournaments/{id}/verify-chain re-checks the night, and the integrity panel’s invariants — chip conservation among them — are queryable, not marketing.
Exports Everything out, always: results, seasons, players, the lot. Dual-run Felt alongside your current system and export each night in the format it expects.

The AsyncAPI document for the realtime protocol publishes alongside openapi.json.