API keys
The simplest credential, and the right one when you are automating your own workspace. Per-key scoped, expiring by default, hashed at rest — we cannot show you a key again after you create it because we do not have it.
Authorization: token reos_live_…
Rate limit: 120 requests/minute per key.
OAuth2
For an application acting on other workspaces' behalf. Register it in Settings → Developers and you get a client_id and a client_secret (shown once).
Authorization code + PKCE
The flow for anything with a user in front of it, including CLIs.
- Send the person to:
https://app.zellerai.co/api/oauth/authorize
?client_id=app_…
&redirect_uri=https://yourapp.example/callback
&response_type=code
&scope=contacts:read%20messages:send
&state=<random>
&code_challenge=<base64url(sha256(verifier))>
&code_challenge_method=S256
- They approve the scopes on a consent screen that lists them in plain English.
- We redirect back with
?code=…&state=…. - Exchange it:
curl -X POST https://app.zellerai.co/api/oauth/token \
-d grant_type=authorization_code \
-d client_id=app_… \
-d code=reos_ac_… \
-d redirect_uri=https://yourapp.example/callback \
-d code_verifier=<verifier>
code_challenge_method=S256 only. plain is refused, not merely unsupported — it proves only that you can echo a string you already sent in the clear.
Loopback redirect URIs match on any port. Register http://127.0.0.1:1/callback and a CLI can bind whatever port it gets (RFC 8252 §7.3). Scheme, host and path still match exactly.
Refresh
curl -X POST https://app.zellerai.co/api/oauth/token \
-d grant_type=refresh_token \
-d client_id=app_… \
-d client_secret=reos_cs_… \
-d refresh_token=reos_rt_…
Refresh tokens rotate: each exchange returns a new one and retires the old. Presenting a retired refresh token revokes the entire family — we cannot tell a race from a theft, so we assume the worse one. Handle this by storing the new token before you use it.
Client credentials
For a server-to-server integration with no user present. Requires that the workspace has already authorized your app through the flow above — there is no path from a client id to a workspace's data without a consent record.
curl -X POST https://app.zellerai.co/api/oauth/token \
-d grant_type=client_credentials \
-d client_id=app_… \
-d client_secret=reos_cs_… \
-d tenant_id=clx… # required when more than one workspace has authorized you
HTTP Basic works too: Authorization: Basic base64(client_id:client_secret).
Revoking
curl -X POST https://app.zellerai.co/api/oauth/revoke -d token=reos_at_…
Always returns 200, even for a token that never existed — anything else would be a free token-validity checker.
A workspace can revoke your app at any time from Settings → Developers. Revocation takes effect on the next request, not when the access token expires: we re-read the grant every time.
Scopes
contacts:read— See your contacts and their detailscontacts:read:pii— See full phone numbers and email addresses instead of masked onescontacts:write— Create and update contacts, and add notes and tagsleads:read— See incoming leads and how they were routedleads:write— Create leads and assign them to people on your teamevents:read— See activity timelines — calls, messages, notes and status changesmessages:read— Read your texts and emails with contactsmessages:send— Send texts and emails to your contacts on your behalfcalls:read— See call records, recordings, transcripts and AI summariesdeals:read— See your pipelines and the deals in themdeals:write— Create and update deals and move them between stageslistings:read— See the properties in your workspaceappointments:read— See appointments and tasksappointments:write— Book, reschedule and cancel appointments, and create tasksautomations:read— See your automations and the campaigns and personas they useautomations:manage— Start automations for a contactwebhooks:manage— Create and remove webhook subscriptions that receive your workspace's events
Some scopes imply others: *:write implies *:read, messages:send implies messages:read, automations:manage implies automations:read, and contacts:read:pii implies contacts:read. Request the narrowest set that works — a consent screen listing fewer sentences gets approved more.