Docs

Authentication

Getting a token, sending it, and the scopes that decide what it can reach.

Authentication is a JSON Web Token obtained by signing in. There are no API keys.

Getting a token

curl -X POST https://your-domain.example/api/login \
  -H 'Content-Type: application/json' \
  -d '{"email":"[email protected]","password":"..."}'
{
  "token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...",
  "user": { "email": "[email protected]", "scope": "restaurant", "...": "..." }
}

Using it

Send it as a bearer token on every request:

curl https://your-domain.example/api/orders \
  -H 'Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...'

Scopes

The token carries a scope that decides what it can reach:

ScopeWhoReaches
platformHappy Voice staffEverything, across all businesses
businessA resellerTheir own business: restaurants, users, domains, plans, credentials
restaurantA restaurantThat restaurant's own data only

Tokens are bound to a tenant

A token minted on one business's host is rejected on another's with 403. The Host header is part of the authorisation decision, not just routing — you cannot lift a token from one tenant and replay it against another.

Send requests to the same host you authenticated against.

Responses you should handle

StatusMeaningWhat to do
401Missing, expired or invalid tokenSign in again
403Valid token, wrong tenant or insufficient scopeCheck the host and the account's scope
402SUBSCRIPTION_REQUIRED — restaurant has no active subscriptionWrites are blocked until billing is resolved; reads still work
403BUSINESS_SUSPENDED — the reseller is past dueThe whole tenant is read-only until payment succeeds

402 and 403 BUSINESS_SUSPENDED are the two that surprise people: the API stays readable but stops accepting writes. Treat them as a billing state, not an auth bug.

Unauthenticated endpoints

A handful of endpoints are public because the voice runtime or an external service needs them without a session — health, tenant branding, and the Twilio and Stripe webhooks. They are marked in the API reference.

On this page