Authentication
Authenticate to the B2B API with an organization-scoped API key, choose a scope, and select the workspace you operate on.
Every /api/v1 request is authenticated with an API key. A key belongs to a user and acts with that user’s role in the workspace it targets. It never grants more than the user has in the dashboard.
Keys start with ft_live_. FreeTicket stores only a hash, so the secret is shown once, when the key is created.
Get a key
There are two ways to get a key. Both need a FreeTicket account with access to at least one workspace.
npm install -g @freeticket/cli
ft login # approve in the browser
ft api-keys create my-integration --scope writeft login uses the OAuth 2.0 device flow: it prints a short code, opens your browser, and you approve the session where you are already signed in. Then ft api-keys create mints a dedicated key for your integration and prints the secret once.
Optional: --expires 2027-01-01 sets an expiry date.
If you already hold a key, mint another one with POST /api-keys:
curl -X POST https://admin.appfreeticket.com/api/v1/api-keys \
-H "Authorization: Bearer ft_live_xxx" \
-H "Content-Type: application/json" \
-d '{"name": "my-integration", "scope": "write"}'The plaintext key is only in this response. Creating keys requires the ADMIN role.
List and revoke keys with ft api-keys list and ft api-keys revoke <id>, or GET /api-keys and DELETE /api-keys/{id}.
Send the key
Send the key as a bearer token:
Authorization: Bearer ft_live_xxx
The raw key in an x-api-key header also works:
x-api-key: ft_live_xxx
A missing, invalid, expired or revoked key returns 401.
Scopes
Each key has one scope:
| Scope | Allows |
|---|---|
read |
GET and HEAD only |
write |
Reads, plus create, update, delete, publish, cancel, refund and check-in |
A read key that calls a mutating method gets 403 with the code READ_ONLY_KEY. Keys created with POST /api-keys or ft api-keys create default to read. The session that ft login stores in the CLI can write.
The scopes are also published in machine-readable form at https://admin.appfreeticket.com/.well-known/oauth-protected-resource (RFC 9728).
Roles
Scope says what a key may do. The user’s role in the target workspace says what it can do. Each operation in the reference states its minimum role:
| Role | Typical access |
|---|---|
VIEWER |
Read events, ticket types, sales lists, plans, venues and most reports |
STAFF |
Plus sale details, a sale’s tickets, ticket access checks and door check-in |
ADMIN |
Plus every write, staff management, exports, settlements, discounts, webhooks and API keys |
The role is resolved on every request, so changing a user’s role takes effect without reissuing their keys. A role below the minimum returns 403. If the user’s access to the workspace expired or doesn’t cover a section, the API returns 403 with ACCESS_EXPIRED or SECTION_FORBIDDEN.
Workspaces
A user can belong to several workspaces. Choose one per request with the X-Workspace-Id header:
curl https://admin.appfreeticket.com/api/v1/events \
-H "Authorization: Bearer ft_live_xxx" \
-H "X-Workspace-Id: <workspace-id>"
Without the header, the request runs against the user’s default workspace. GET /me lists every workspace the key can reach, with the user’s role in each:
curl https://admin.appfreeticket.com/api/v1/me \
-H "Authorization: Bearer ft_live_xxx"
{
"data": {
"userId": "…",
"name": "Ana",
"email": "ana@example.com",
"role": "ADMIN",
"activeWorkspaceId": "…",
"workspaces": [
{ "id": "…", "name": "My Venue", "slug": "my-venue", "role": "ADMIN", "sections": null }
]
}
}
In each workspace row, sections: null means unrestricted access and [] means access has expired or been revoked. Use the role in each row. The top-level role is deprecated.
Keep keys safe
- Keep keys server-side. Never ship them in a browser bundle or a mobile app. For buyer-facing flows, use the public storefront API, which needs no key.
- Prefer
readkeys for dashboards, reporting and BI. - Set an expiry on keys for temporary work, and revoke the keys you no longer use.

