The API accepts two authentication credentials — both carried in the
Authorization header.
Authorization: Bearer psg_live_ab12…
Keys are issued per organization from Developer → API Keys in the admin console. They are prefixed:
psg_live_… — production key.psg_test_… — sandbox / staging key.Scopes: each key is created with a fixed scope set — equipment:read,
webhooks:write, etc. The API rejects a request whose target endpoint
requires a scope the key doesn't hold with 403 FORBIDDEN.
Expiry: a key may carry an expiry date, set when it is created. It works
until the end of that day in the time zone of the browser that created it;
from then on every request with it returns 401 UNAUTHORIZED.
Rotation: keys are opaque and cannot be read again after creation. To rotate, issue a new key, deploy it, then revoke the old one. Revocation is immediate.
Interactive sessions in the admin console carry a Firebase ID token:
Authorization: Bearer eyJhbGci…
These tokens are short-lived and auto-refreshed by the client. Third-party integrations should use API keys, not Firebase tokens.
| Header | Required | Purpose |
| -------------------------------- | -------------------------------------- | ------------------------------------------------------------------- |
| Authorization: Bearer <token> | Always | Firebase ID token or API key |
| X-Org-Id: <uuid> | Depends on credential — see below | Selects which organization's tenant you're addressing |
| Idempotency-Key: <uuid> | Every mutation (POST/PATCH/PUT/DELETE) | Replay-safety; UUID v4 |
| Accept-Language | Optional | Returns localized error messages (see Errors) |
| Content-Type: application/json | On requests with a body | |
API keys ignore X-Org-Id entirely. A key is bound to one organization
at creation time, and every request made with it is scoped to that org — the
header is never read for API key requests.
Firebase ID tokens treat X-Org-Id as optional. If omitted, the API
falls back to your account's default organization, then to your first active
org membership. Set the header explicitly when your account belongs to more
than one organization and you need to target a specific one. A value that
isn't a well-formed UUID returns 400 BAD_REQUEST.
If you hold a key for Org A and request a resource owned by Org B, you
receive 404 NOT_FOUND — not 403. This is deliberate to avoid
leaking the existence of cross-tenant resources.