wacrm/docs/public-api.md
Arnas Donauskas 456321a2f2
feat(api): public API groundwork — scoped API keys + /api/v1/me
Adds the machine-to-machine auth layer the public REST API needs so
external scripts and automations can drive wacrm (issue #245). This is
the groundwork release; data endpoints land one at a time on top of it.

- Migration 026: account-scoped `api_keys` table. SHA-256 hash at rest
  (plaintext shown once), display `key_prefix`, `scopes[]`, RLS via
  `is_account_member` (members read, admin+ manage).
- `src/lib/api-keys/`: key gen/hash (wacrm_live_ prefix, timing-safe),
  scope vocabulary, service-role auth-path store.
- `requireApiKey(request, scope?)` (src/lib/auth/api-context.ts):
  bearer parse -> hash lookup -> liveness -> per-key rate limit ->
  scopes-only authz. Returns a service-role-scoped account context.
- `/api/v1` envelope ({data} / {error:{code,message}}) + GET /api/v1/me
  reference endpoint that exercises the whole stack.
- Dashboard: admin+ key management routes + Settings -> API keys tab.
- Docs (docs/public-api.md), CHANGELOG, README, .env note.

Checks: typecheck clean, lint 0 errors, full suite green (+27 tests).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-23 15:35:43 +03:00

131 lines
4.5 KiB
Markdown

# Public API (`/api/v1`)
The public API lets you drive your wacrm instance from your own
scripts and automations — send messages, manage contacts, launch
broadcasts — without going through the dashboard UI.
> **Status:** groundwork release. Authentication, scopes, rate
> limiting, and the `GET /api/v1/me` probe ship now. The data
> endpoints (`messages`, `contacts`, …) land one at a time in
> follow-up releases — see [Roadmap](#roadmap).
## Authentication
Every request authenticates with an **API key**, sent as a bearer
token:
```
Authorization: Bearer wacrm_live_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
```
Keys are **account-scoped**: a key acts on exactly one account, the
one it was created in. There is no cross-account access.
### Creating a key
In the dashboard: **Settings → API keys → New API key**. Only
**admins and owners** can create keys.
1. Give the key a name (after the integration that will use it).
2. Grant the **scopes** it needs — nothing more (see below).
3. Copy the key. **The full key is shown exactly once.** wacrm
stores only a SHA-256 hash, so it can never be shown again. If you
lose it, revoke it and create a new one.
### Revoking a key
**Settings → API keys → Revoke.** Revocation is effective on the
key's next request. Revoked keys stay in the list as an audit trail.
## Scopes
A key can do only what its scopes allow — independent of who created
it. Grant the minimum.
| Scope | Allows |
| -------------------- | ---------------------------------------- |
| `messages:send` | Send WhatsApp messages |
| `messages:read` | Read messages and delivery status |
| `contacts:read` | List and read contacts |
| `contacts:write` | Create and update contacts |
| `conversations:read` | List and read conversations |
| `broadcasts:send` | Launch broadcast campaigns |
A key with **no scopes** still authenticates and can call
`GET /api/v1/me` — useful for verifying a key works.
## Response envelope
Every response uses one of two shapes:
```jsonc
// success
{ "data": { /* ... */ } }
// failure
{ "error": { "code": "forbidden", "message": "This API key is missing the 'messages:send' scope" } }
```
Branch on `error.code` (stable); `error.message` is for humans and
may be reworded.
| Status | `code` | Meaning |
| ------ | -------------- | ------------------------------------------------ |
| 401 | `unauthorized` | Missing / malformed / unknown / revoked / expired key |
| 403 | `forbidden` | Valid key, but missing the required scope |
| 429 | `rate_limited` | Per-key rate limit exceeded |
| 400 | `bad_request` | Malformed input |
| 404 | `not_found` | No such resource |
| 500 | `internal` | Server error |
## Rate limits
Requests are limited **per key**: **120 requests per minute**. On a
`429`, these headers tell you when to retry:
- `Retry-After` — seconds until the window resets
- `X-RateLimit-Limit`, `X-RateLimit-Remaining`, `X-RateLimit-Reset`
> The limiter is in-memory and **per process**. A single-instance
> deploy (the common case for a self-hosted fork) is fine as-is. If
> you scale to multiple instances, swap the limiter for a shared
> store (Redis/Upstash) — see the note at the top of
> `src/lib/rate-limit.ts`. The limit is otherwise unenforced across
> instances.
## Endpoints
### `GET /api/v1/me`
Returns the account a key is bound to and the scopes it carries.
Requires only a valid key (no scope). Use it to verify a key works
and to discover its scopes.
```bash
curl https://your-crm.example.com/api/v1/me \
-H "Authorization: Bearer wacrm_live_xxx"
```
```json
{
"data": {
"account": { "id": "…", "name": "Acme Inc" },
"key": { "id": "…", "scopes": ["messages:send"] }
}
}
```
## Roadmap
Planned endpoints, shipping one per release (tracked in
[#245](https://github.com/ArnasDon/wacrm/issues/245)):
- `POST /api/v1/messages` — send a message to a phone number
(`messages:send`)
- `GET/POST /api/v1/contacts`, `GET/PATCH /api/v1/contacts/{id}`
(`contacts:read` / `contacts:write`)
- `GET /api/v1/conversations` (`conversations:read`)
- `POST /api/v1/broadcasts` (`broadcasts:send`)
- Outbound event webhooks (so automations can react to inbound
messages)