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

4.5 KiB

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.

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:

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

curl https://your-crm.example.com/api/v1/me \
  -H "Authorization: Bearer wacrm_live_xxx"
{
  "data": {
    "account": { "id": "…", "name": "Acme Inc" },
    "key": { "id": "…", "scopes": ["messages:send"] }
  }
}

Roadmap

Planned endpoints, shipping one per release (tracked in #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)