API keys and scopes

Issue a key that can do exactly one thing, send it as a bearer token, and read the one error shape.

An API key lets your own software read or write your workspace through https://<your-workspace>.monody.app/api/v1/integrations/*. The owner issues it under Account → API keys, picks what it may do, and the key is shown once. Send it on every request:

Authorization: Bearer monody_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx

Scopes are the vocabulary of what a key may do. Pick the least the integration needs — a landing page that posts leads needs leads:write and nothing else.

  • leads:read — list leads and read their status (GET /api/v1/integrations/leads, paged with cursor and limit, filtered with status)
  • leads:write — create a lead (POST /api/v1/integrations/leads)
  • members:read, bookings:read, webhooks:manage — reserved: they are in the vocabulary so a key can be issued for them today, and the form says no endpoint reads them yet
  • * — everything, present and future; for a tool you trust with all of it

A scope outside this list cannot be issued, and an old key carrying free text grants nothing for it.

Creating a lead with a key. POST /api/v1/integrations/leads with a JSON body: firstName, lastName (up to 120 characters each), email (stored lower-cased), phone (as typed — normalised to E.164 when it parses), source (defaults to campaign) and consent ({ marketing_email, marketing_sms } booleans; recorded as granted via the API). At least a name, an email or a phone is required. The answer is 201 with the lead: id, firstName, lastName, email, phone, source, status, score, duplicateOfId (the open lead this one matches, when it does) and createdAt. A lead.created webhook goes out for it.

Every refusal is one shape, whatever went wrong:

{ "error": { "code": "missing_scope", "message": "This key does not have the leads:read scope. Issue a key with it under Account → API keys.", "field": null } }

Codes: missing_api_key and invalid_api_key (401 — no bearer, or a revoked or expired key), missing_scope (403), invalid_json and validation_failed (400 — field names the field), tenant_not_found (404). The message is written to be shown; code is what your code branches on.

Revoking. Revoke a key from its row; every request with it answers invalid_api_key from that moment. The row shows when the key was last used, so a key nothing has used in months is easy to spot and retire.