Authentication
Every operation except the marketplace catalogue needs a credential in the Authorization header. There are
three kinds.
| API key | Personal access token | OAuth access token | |
|---|---|---|---|
| Header | Bearer zk_live_… / Bearer zk_test_… | Bearer zpat_… | Bearer <JWT> |
| Acts as | your organisation | the person who made it | the person who signed in to your app |
| Made in | Developer Studio (live and test); the console's Team → New API key (live) | POST /v1/api-tokens | your app, through auth.zaitrack.app |
| Lifetime | 1 to 365 days (default 365) | 1 to 365 days | short-lived; refreshed with a refresh token |
| Use it for | server-to-server integrations | scripts you run as yourself | apps people sign in to |
Whatever the kind, a credential never carries a scope wider than its creator's.
API keys
An API key belongs to the organisation, not to a person. It looks like zk_live_ (or zk_test_), 32 base32
characters, _ and a 16-character checksum; the gateway rejects a malformed key without a lookup.
- Scopes are chosen when the key is made, no wider than yours.
- IP allowlist and expiry are optional; a request from another address gets
403and an expired key401. - Rotate with
POST /v1/api-keys/{id}:rotate: the new secret is shown once and keeps the old key's scopes. The old key keeps working for 30 days, or until it expires if that is sooner, so you can switch without downtime. - Revoke with
POST /v1/api-keys/{id}:revoke. A revoked key stops working within 30 seconds (the gateway's caches).
Keys are created in Developer Studio, live or test (a test key on one of your sandboxes), or as live keys in the console under Team → New API key. The API has no call to create one.
Personal access tokens
A personal access token (zpat_…) acts as you, with at most your scopes, and every use is audited. Make one with
POST /v1/api-tokens while signed in through OAuth: the service refuses API keys and other non-interactive
credentials there.
curl -X POST "https://api.zaitrack.app/v1/api-tokens" \
-H "Authorization: Bearer $ACCESS_TOKEN" \
-H "Content-Type: application/json" \
-d '{"label": "Fuel import script", "scopes": ["fuel:read", "fuel:write"], "ttlDays": 30}'
The token is in the response once. List yours with GET /v1/api-tokens; revoke one with
POST /v1/api-tokens/{id}:revoke.
OAuth apps
Register an app in Developer Studio (OAuth apps → Register an app) for software people sign in to.
- Issuer:
https://auth.zaitrack.app/(Ory Hydra). Read the endpoints fromhttps://auth.zaitrack.app/.well-known/openid-configuration. - Grant: authorization code, with refresh tokens. There is no client-credentials grant for apps: a server with no person behind it uses an API key.
- Public apps (single-page, mobile, desktop) have no secret and use PKCE (send
code_challenge_method=S256). - Confidential apps (a server-side web app) get a client secret, shown once, sent with HTTP Basic
(
client_secret_basic). Rotate shows a new one. - Redirect URIs are
https, orhttp://localhostfor development. - Scopes are no wider than what your organisation's roles grant and what you, registering the app, hold.
- Who signs in: only members of your organisation. There is no consent screen yet.
- Delete removes the app and revokes its refresh tokens at once; an access token already issued lasts until it expires (15 minutes).
Scopes
A scope is <family>:<action>, for example vehicles:read. A <family>:* scope covers every action of the
family: alerts:* covers alerts:read and alerts:write. A request without the operation's scope gets
403 with code auth.token.scope_missing and the missing scopes in detail.
The API reference lists each scope and how many operations need it; every operation's page names its own.
Step-up MFA
Sending a device command and approving one (POST /v1/commands, POST /v1/commands/{id}:approve) need a recent
WebAuthn step-up. Only an OAuth access token from a session that passed it qualifies; an API key never does.
Without it the gateway answers 403 with code step_up_required and a WWW-Authenticate: Step-Up … header.