Authentication & scopes
How API keys authenticate requests and what each scope grants.
API keys
The Public API authenticates with API keys only. Send the key as a bearer token:
Authorization: Bearer strd_<key>X-API-Key: strd_<key> is also accepted. Keys are created and managed in
Settings → API keys.
- Team-scoped. The team is resolved from the key. Every request is implicitly scoped to that team; cross-team access is impossible.
- No session cookies. The public surface never reads or falls back to a browser session. A cookie on a public request is ignored.
- Shown once. The full secret is returned only at creation. Store it securely; it cannot be retrieved again. If you lose it, revoke the key and create a new one.
Who a key acts as
Keys are created by team admins and owners only (a regular member or restricted role cannot mint one). A key is a workspace credential that carries the explicit scopes chosen at creation and acts as its creator. For most resources that is the same team-wide access any member has; for permission-gated data (time tracking rates and another employee's entries, and travel) the key acts with its admin creator's rights, resolved live at request time. A key reading trips sees the team's trips only while its creator is still a travel manager; otherwise it sees that person's own trips. If the creator later loses that access or leaves the team, those calls fail closed.
A key does not outlive its creator's membership. Every request re-resolves whether
the creator is still an active member of the key's team, so removing or disabling them
makes the key answer 401 immediately, on every surface. Removing or disabling a member
also revokes their keys, and that revocation is permanent: re-inviting or re-enabling the
person does not bring their old keys back, so plan on issuing a new key and updating
whatever used the old one. Per-user personal access tokens, bounded by the holder's own
permissions, are planned as a separate capability.
Scopes
A key holds a set of fine-grained resource:action scopes. The grammar is flat:
there are no wildcards. The tokens are every combination of:
- Resources:
organizations,contacts,employees,projects,tasks,activities,assignments,cvs,time_entries,files,attachments,trips - Actions:
read,write
For example organizations:read or files:write. (A key can also carry an imports
scope, but that resource is reserved for connected apps and has no public REST or
GraphQL endpoint, so it is not part of the surface documented here.)
tripsis read-only. A trip is created, approved, and sealed in the app, sotrips:readis the only token that exists for it.trips:writeis not a scope: it cannot be granted, and requesting it is rejected.writeimpliesreadfor the same resource (and only that resource). A key holdingcvs:writemay also read CVs. It grants nothing on any other resource.- REST. Each endpoint requires the matching token: a
GETneeds<resource>:read, a write needs<resource>:write. A key missing the token gets403 forbidden. - GraphQL. Enforced per resolver; a query needs the resource's
:read, a mutation its:write. The single endpoint authenticates the key; each field then asserts its own scope. - Search.
searchhas no scope of its own. Results are filtered to the resource types the key can read; a key that can read none of the searchable types (organizations, contacts, employees, cvs, projects) gets403 forbidden.
Because scopes carry no wildcards, a key is only ever granted the resources named at creation time. When a new resource is added to the API, existing keys do not automatically gain access; they keep working unchanged and must be re-issued to opt in. This is an additive, non-breaking change; see versioning.
Key limits
Two ceilings apply, and on every self-serve plan the one you meet is the plan's.
Your plan's key allowance is the binding one. It is listed with the other plan limits
on the Steerd pricing page and under Settings → Billing, and a team at its allowance
gets 402 limit_reached. Its limit, current and upgrade_to fields state the
ceiling and what would raise it, so the refusal itself is the authoritative number (see
errors). A plan that does not include API access at all
refuses earlier, with 403 feature_unavailable.
Above that sits an operational ceiling of 50 active keys (non-revoked, non-expired)
per team, which returns 409 key_limit_reached. It exists as a backstop for plans whose
allowance is unlimited, so it is not the number to design against.
Either way, revoking a key or letting one expire frees a slot.
A connected app consumes an allowance slot. Authorizing one mints a key in the authorizing member's name, so a team can reach its allowance without anyone having opened the API keys screen.
Plan for rotation against your plan's allowance, not against the 50.