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/*. Issue it under Settings → Forms and API → API keys, pick what it may do, and copy it: the key is shown once.

Sending the key

Send it on every request:

HTTP
Authorization: Bearer monody_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx

Scopes

Scopes are the vocabulary of what a key may do. Pick the least the integration needs: a tool that creates leads needs leads:write and nothing else.

  • leads:read — list leads and read their status
  • leads:write — create a lead
  • members:read, bookings:read, webhooks:manage — reserved: a key can carry 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 through 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. A lead created this way is not assigned to a rep or sent the instant reply; a page that wants both posts to the Lead capture API instead.

Reading leads

GET /api/v1/integrations/leads with leads:read lists leads newest first: limit (1 to 200, default 50), status to filter, and cursor set to the next_cursor of the page before. The answer is { data, next_cursor }.

Errors

Every refusal is one shape, whatever went wrong:

JSON · 403
{ "error": { "code": "missing_scope", "message": "This key does not have the leads:read scope. Issue a key with it under Settings → Forms and API → API keys." } }
CodeStatusWhen
missing_api_key401No bearer token was sent.
invalid_api_key401The key is revoked, expired or not this workspace's.
missing_scope403The key does not carry the scope the call needs.
invalid_json400The body is not a JSON object.
validation_failed400A field is wrong; field names it.
tenant_not_found404No workspace at that address.
CodeStatusWhen
missing_api_key401No bearer token was sent.
missing_api_key 401 No bearer token was sent.
invalid_api_key401The key is revoked, expired or not this workspace's.
invalid_api_key 401 The key is revoked, expired or not this workspace's.
missing_scope403The key does not carry the scope the call needs.
missing_scope 403 The key does not carry the scope the call needs.
invalid_json400The body is not a JSON object.
invalid_json 400 The body is not a JSON object.
validation_failed400A field is wrong; field names it.
validation_failed 400 A field is wrong; field names it.
tenant_not_found404No workspace at that address.
tenant_not_found 404 No workspace at that address.

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.

A question these pages do not answer? Write to support@monody.app.