Steerd API

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.)

  • trips is read-only. A trip is created, approved, and sealed in the app, so trips:read is the only token that exists for it. trips:write is not a scope: it cannot be granted, and requesting it is rejected.
  • write implies read for the same resource (and only that resource). A key holding cvs:write may also read CVs. It grants nothing on any other resource.
  • REST. Each endpoint requires the matching token: a GET needs <resource>:read, a write needs <resource>:write. A key missing the token gets 403 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. search has 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) gets 403 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.

On this page