Authentication
API keys v2 — scopes, allowed uses, expiry, rotation — and OAuth Bearer
The Servicebay API accepts API keys (X-API-Key) or OAuth 2.1
access tokens (Authorization: Bearer). Every /api/v1/** call also
requires the organisation to be on Pro or an in-app trial.
Pro plan or in-app trial required
Paid statuses active / past_due / trialing resolve to the org
subscriptionTier. Otherwise an in-app trial applies when there is no
Stripe subscription (or the status is incomplete /
incomplete_expired) and now is before trialEndsAt (or
createdAt + 30 days). Paid Basic orgs stay locked out. Failed
checks return 403 — see
Error Handling.
Getting an API key
Sign in to the developer portal
Open developer.servicebay.io/login
and sign in with your Servicebay credentials. TOTP is required —
see Two-Factor Authentication. You must be an
active admin (userRole === 'admin' and active === true).
Pick an organisation
Each key is bound to a single organisation. Choose the org from the dropdown. The selector only lists Pro / trial orgs you actively admin.
Create the key
Click Create API Key. The wizard defaults to Read only, 90-day expiry, and REST + MCP. Step through:
- Label (and optional description)
- Permission presets — Read only, Read & write, Full, or Custom (per-resource matrix)
- Expiry, optional IP allowlist (CIDR or single IPs, max 20), and
allowed use (
rest,mcp, or both)
Copy the secret. It is shown only once.
Using your API key
curl -X GET \
'https://developer.servicebay.io/api/v1/organisations/{orgId}/customers' \
-H 'X-API-Key: sk_live_your_api_key_here'The same sk_live_… secret works as Authorization: Bearer at
https://oauth.servicebay.io/mcp when allowedUses includes mcp. A
REST-only key is rejected at MCP (use_not_allowed). An MCP-only key is
rejected on REST with the same code.
Key format
Production keys begin with sk_live_ followed by 32 hexadecimal
characters (randomBytes(16)):
sk_live_a1b2c3d4e5f6g7h8i9j0k1l2m3n4o5p6Treat the entire string as a single secret. The portal stores only a SHA-256 hash and a 16-character prefix.
Scopes, uses, and expiry
New keys are created at scope version 2:
| Field | Default | Notes |
|---|---|---|
scopes | ["read"] | Bundles read / write / full, or per-entity cells. write does not include delete |
allowedUses | ["rest", "mcp"] | At least one of rest, mcp |
expiresInDays | 90 | 30, 90, 365, or null (never). Or an expiresAt ISO datetime at most one year ahead |
ipAllowlist | [] | CIDR or single IPs, max 20. Empty means any IP |
Legacy keys with no scopes array keep full access at v1
(write still implies delete). They appear as legacy: true in the
portal.
An organisation may have at most 50 active keys. Revoke one before creating another.
Management endpoints
These use a Firebase ID token (TOTP) on the developer portal,
not X-API-Key.
| Method | Path | Purpose |
|---|---|---|
| POST | /api/v1/api-keys | Create (secret returned once) |
| GET | /api/v1/api-keys?organisationId= | List |
| PATCH | /api/v1/api-keys/{keyId} | Edit name, description, scopes (widen or narrow), allowedUses, ipAllowlist, expiresAt, active |
| POST | /api/v1/api-keys/{keyId}/rotate | Mint a new secret. Body { "graceMinutes": 0 } — 0, 15, 60, or 1440. The old secret keeps working until grace ends |
| DELETE | /api/v1/api-keys/{keyId} | Soft-revoke (active: false, revokedAt, revokedBy). The row stays listed |
Rotate after a leak. Grace 0 revokes the old secret immediately.
401 vs 403
401 — the credential is not usable
| Situation | Body |
|---|---|
| Missing / malformed | Missing credentials. Provide an X-API-Key header or an OAuth Bearer token. / Invalid API key format. |
| Revoked or paused | This API key has been revoked or paused. |
| Expired | This API key has expired. |
| Bad OAuth token | Invalid or expired access token. |
401 responses include:
WWW-Authenticate: Bearer error="invalid_token", resource_metadata="https://developer.servicebay.io/.well-known/oauth-protected-resource"403 — the credential is valid but not allowed
code | When |
|---|---|
insufficient_scope | Route needs a scope the key/grant does not hold. Body includes requiredScopes. Header: WWW-Authenticate: Bearer error="insufficient_scope", scope="…" |
unknown_resource | Path is not in the deny-by-default route map |
use_not_allowed | REST-only key used at MCP, or MCP-only key used on REST |
ip_not_allowed | Caller IP is outside ipAllowlist |
| (no code) | Org is not Pro / trial, or the key's org does not match the path, or the OAuth admin is no longer an active admin |
{
"success": false,
"error": "This key is REST-only",
"code": "use_not_allowed"
}OAuth Bearer
Send Authorization: Bearer <access_token> instead of X-API-Key.
Everything else about the API is identical. Tokens are
audience-bound to https://oauth.servicebay.io/mcp; REST accepts that
audience. See OAuth.
Actor identity
API-written records (timeline messages, recurring actions, signatures)
carry the key creator's Firebase uid (createdBy), or the OAuth
consenting admin's uid. There is no synthetic actor. Keys survive the
admin who created them — when an admin leaves, rotate every key they
created. Remaining admins can see createdBy and lastUsedAt in the
portal.
Admin access and TOTP
Only active users with the admin role can manage keys. Deactivated or invited-but-not-activated admins are rejected. Members can use existing keys but cannot create or revoke them.
Management routes return 403 if the Firebase ID token was not issued
after a TOTP second factor.
2FA only protects the dashboard. API requests using X-API-Key are
not challenged because the API key itself is the credential.