# Authentication

## 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.

1. 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
```

2. They approve the scopes on a consent screen that lists them in plain English.
3. We redirect back with `?code=…&state=…`.
4. Exchange it:

```bash
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

```bash
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.

```bash
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

```bash
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 details
- `contacts:read:pii` — See full phone numbers and email addresses instead of masked ones
- `contacts:write` — Create and update contacts, and add notes and tags
- `leads:read` — See incoming leads and how they were routed
- `leads:write` — Create leads and assign them to people on your team
- `events:read` — See activity timelines — calls, messages, notes and status changes
- `messages:read` — Read your texts and emails with contacts
- `messages:send` — Send texts and emails to your contacts on your behalf
- `calls:read` — See call records, recordings, transcripts and AI summaries
- `deals:read` — See your pipelines and the deals in them
- `deals:write` — Create and update deals and move them between stages
- `listings:read` — See the properties in your workspace
- `appointments:read` — See appointments and tasks
- `appointments:write` — Book, reschedule and cancel appointments, and create tasks
- `automations:read` — See your automations and the campaigns and personas they use
- `automations:manage` — Start automations for a contact
- `webhooks: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.
