API keys

Every request to the REST API is authenticated with an API key. Keys belong to your organization, carry an access level, and act on the API like an owner of the workspace — treat them like passwords.

Creating a key

Only workspace owners can manage keys. In the app, open API keys in the sidebar and create a key with:

The full key is shown exactly once, right after creation. Copy it into your secret manager — afterwards only a short prefix remains visible, and a lost key has to be revoked and re-created.

Access levels

A key's level decides which endpoints it may call. A request below the required level is rejected with 403.

readQuery endpoints (GET) — reporting and exports.
writeQueries plus mutations (POST), except administrative endpoints.
adminEverything, including the administrative routers below.

These routers always require an admin key — even for reads, because they expose organization administration and personnel data such as contracts and absences:

members      invitations  organization    webhooks
apiKeys      sso          auditLog        employees
contracts    absences     daysOff         holidayCalendars

Platform administration, the feedback relay and onboarding are not part of the public API at all — those routes answer 404 for every key.

Authenticating requests

Send the key with every request, either as a bearer token in the Authorization header or in the x-api-key header:

curl -H "Authorization: Bearer $KEY" \
  https://app.example.com/api/v1/customers/list

Making requests

The API lives under /api/v1 on the same origin as the app. Every endpoint is a router and procedure pair; queries are called with GET, mutations with POST.

Query input travels URL-encoded in the input parameter, mutation input as the JSON request body:

# A query with input
curl -H "Authorization: Bearer $KEY" \
  --get https://app.example.com/api/v1/timeEntries/list \
  --data-urlencode 'input={"from":"2026-01-01","to":"2026-01-31"}'

# A mutation
curl -X POST -H "Authorization: Bearer $KEY" \
  -H "content-type: application/json" \
  -d '{"name":"Acme Inc."}' \
  https://app.example.com/api/v1/customers/create

Successful responses wrap the result in a data envelope:

{ "data": { "id": "…", "name": "Acme Inc." } }

Errors and rate limits

Errors come back as JSON with a message and a machine-readable code — always in English, regardless of any locale settings:

{ "error": "Rate limit exceeded", "code": "TOO_MANY_REQUESTS" }
400Invalid input — the body or input parameter failed validation.
401Missing, invalid, expired or revoked key.
403The key's level is too low for this endpoint.
404Unknown router or procedure.
405Wrong method — GET for queries, POST for mutations.
429Rate limit exceeded.

Each key may make 1,000 requests per hour. Beyond that the API answers 429 with a Retry-After header telling you how many seconds to wait.

Key lifecycle

A key acts on behalf of the owner who created it, and audit-log entries for its requests are attributed accordingly.

If that owner leaves the workspace, loses the owner role or is banned, the key stops working immediately with 401 — another owner has to create a replacement.

Revoking a key in the app takes effect immediately.

Next steps

Every endpoint, with schemas and a request playground, lives in the interactive reference: