SocialMate Local API
Authentication & API keys
The x-api-key header, SHA-256 hashed keys, read/send/admin scopes, and per-key rate limits.
Trust in the SocialMate API is the key — not the transport. Every route except
/health and the in-app docs requires a valid x-api-key header,
matched against keys stored on your machine.
The x-api-key header
Pass your key on every request:
curl http://localhost:3456/v1/accounts \
-H "x-api-key: YOUR_KEY"
- Missing header →
401Missing x-api-key header. - Unknown key →
401Invalid API key.
Keys are stored SHA-256 hashed — the plaintext is never written to disk and never returned again after creation. If you lose a key, rotate or recreate it.
Scopes
Each key carries one or more scopes. The server maps every route to the minimum scope it
needs and rejects a key that lacks it with 403 (error.code
insufficient_scope), the body naming what was required and what you
have.
| Scope | Grants |
|---|---|
read | Your WhatsApp data — accounts, chats, contacts, groups, messages, media, queue, antiban, status. |
send | Posting a message (/messages, plus the deprecated /messages/media), enqueuing a queue item, importing a batch, and forcing a media download. |
admin | Everything that mutates — creating/managing groups, queue pause/resume, media cleanup — and reading or writing the server's own configuration: webhooks and API keys, GETs included. |
read is deliberately “your data”, not “everything you can
GET”. Webhook and key listings are admin because they expose how
the server is wired — a webhook row carries its target URL, which is a capability, and the key
list inventories your other credentials.
An unlisted GET defaults to read; any unlisted non-GET
defaults to admin — destructive routes fail closed for read-only keys.
Managing keys over the API
Keys can be managed from the app UI or scripted via the API (the CRUD routes need the
admin scope).
/v1/api-keysList keys with their id, label, scopes, account scope, and last-used time. The key hash is
never returned — only the first-10-character prefix (every key starts
sm_).
Listing keys needs admin. Enumerating the credentials issued
against a server is a configuration read, not a data read — a read-only key you
hand to a dashboard or a contractor must not be able to inventory your other keys.
curl http://localhost:3456/v1/api-keys \
-H "x-api-key: YOUR_KEY"
{ "data": [ { "id": "k_123", "label": "n8n", "prefix": "sm_aB3xK9p", "scopes": ["read","send"], "accountId": "a1b2c3", "accounts": ["a1b2c3"], "createdAt": 1718800000000, "lastUsedAt": 1718805000000, "revoked": false } ] }
/v1/api-keysCreate a key. Body: label (required), scopes (optional array,
defaults to ["read","send"]), accounts (optional array — omit/null
for all accounts, or list ids to scope the key). The plaintext is returned
once; capture it now.
curl -X POST http://localhost:3456/v1/api-keys \
-H "x-api-key: ADMIN_KEY" -H "Content-Type: application/json" \
-d '{ "label": "n8n", "scopes": ["read","send"], "accounts": ["a1b2c3"] }'
{ "data": { "plaintext": "sm_aB3xK9p2QrT5wYz0vN8mLk…", "key": { "id": "k_123", "label": "n8n", "prefix": "sm_aB3xK9p", "scopes": ["read","send"], "accountId": "a1b2c3", "accounts": ["a1b2c3"], "createdAt": 1718800000000, "lastUsedAt": null, "revoked": false } } }
/v1/api-keys/{id}/rotateIssue a fresh secret for an existing key (same id, same scopes). Returns the new
plaintext once; the old value stops working immediately.
curl -X POST http://localhost:3456/v1/api-keys/k_123/rotate \
-H "x-api-key: ADMIN_KEY"
{ "data": { "plaintext": "sm_cD4yL0q3RsU6xZpW1mB7nJ…", "key": { "id": "k_123", "label": "n8n", "prefix": "sm_cD4yL0q", "scopes": ["read","send"], "accountId": "a1b2c3", "accounts": ["a1b2c3"], "createdAt": 1718800000000, "lastUsedAt": 1718805000000, "revoked": false } } }
/v1/api-keys/{id}Revoke and delete a key. Revocation is immediate.
curl -X DELETE http://localhost:3456/v1/api-keys/k_123 \
-H "x-api-key: ADMIN_KEY"
{ "data": { "ok": true } }
Rate limits
Each key is independently rate-limited by a token bucket: 600 requests/minute
sustained (refilling ~10/second, burst 600). Exceeding it returns 429
rate_limited with a Retry-After header and a retryAfterMs
field. This is separate from anti-ban, which governs how fast messages actually go out.
Treat keys like passwords. Use one key per integration, label it clearly, and store it in your automation's secret store or environment — never in client-side code or a public repo. If one leaks, rotate just that key without disrupting the others.