Docs

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.

Building for other people's organisations?

API keys suit integrations you run against your own organisation. If users should connect their Servicebay organisation to your app with scoped, revocable permissions, use OAuth — or MCP for AI agents.

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:

  1. Label (and optional description)
  2. Permission presets — Read only, Read & write, Full, or Custom (per-resource matrix)
  3. 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_a1b2c3d4e5f6g7h8i9j0k1l2m3n4o5p6

Treat 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:

FieldDefaultNotes
scopes["read"]Bundles read / write / full, or per-entity cells. write does not include delete
allowedUses["rest", "mcp"]At least one of rest, mcp
expiresInDays9030, 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.

MethodPathPurpose
POST/api/v1/api-keysCreate (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}/rotateMint 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

SituationBody
Missing / malformedMissing credentials. Provide an X-API-Key header or an OAuth Bearer token. / Invalid API key format.
Revoked or pausedThis API key has been revoked or paused.
ExpiredThis API key has expired.
Bad OAuth tokenInvalid 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

codeWhen
insufficient_scopeRoute needs a scope the key/grant does not hold. Body includes requiredScopes. Header: WWW-Authenticate: Bearer error="insufficient_scope", scope="…"
unknown_resourcePath is not in the deny-by-default route map
use_not_allowedREST-only key used at MCP, or MCP-only key used on REST
ip_not_allowedCaller 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.