Error types
Every non-2xx response from the LeadRails v1 API is an RFC 9457
problem document with a stable type URL. The
type URLs are the machine-readable identifier — pin
against them in your client code. The title field is
human-readable copy and may change.
Shape of a problem response
HTTP/1.1 404 Not Found
Content-Type: application/problem+json
{
"type": "https://docs.leadrails.dev/errors/not-found",
"title": "Resource not found",
"status": 404,
"detail": "No source with id src_01J5Z7N9X3M2VWPQ9YTBN2F0HR in this workspace.",
"instance": "/v1/sources/src_01J5Z7N9X3M2VWPQ9YTBN2F0HR",
"request_id": "req_01J5Z7T7M3M2VWPQ9YTBN2F0HR"
}
Always include request_id when reporting an issue. It is
the single fastest way for us to find your request in the logs.
All problem types
Each row is anchored — link directly to https://docs.leadrails.dev/errors/<slug> and you'll
land on the right row.
| Slug / status | When | Fix |
|---|---|---|
Invalid API key invalid-api-key HTTP 401 | The Authorization header is missing, malformed, or carries a revoked / unknown key. | Confirm the header looks like `Authorization: Bearer lr_live_<rest>`. If it does, the key is revoked or wrong — generate a new one at Settings → API keys. |
Resource not found not-found HTTP 404 | The resource ID is well-formed but does not exist in your workspace. Cross-workspace IDs look identical to missing IDs and resolve here. | Verify the ID by listing the parent collection. If the ID came from another workspace, you cannot use it here. |
Invalid pagination cursor invalid-cursor HTTP 400 | The `cursor` query parameter could not be decoded. Cursors are opaque — only pass back what the API returned in `next_cursor`. | Drop the `cursor` parameter to start from the beginning, or pass the exact value from a previous `next_cursor` response. |
Invalid query parameter invalid-parameter HTTP 400 | A query parameter failed validation — out of range, wrong type, or in an unexpected format. | Check the parameter against the schema in the API reference. The `detail` field names the offending parameter. |
Invalid reference invalid-reference HTTP 400 | A POST/PATCH body references an ID (source_id, destination_id, ...) that exists but belongs to a different workspace, or does not exist at all. | Re-fetch the IDs from the corresponding list endpoint. Make sure every referenced resource lives in the same workspace as the API key. |
Invalid destination adapter type invalid-adapter-type HTTP 400 | The `adapter_type` field on a destination create/update is not one of the known adapter slugs. | Pick a supported `adapter_type` from the API reference (`slack_webhook`, `generic_webhook`, `n8n_webhook`, `housecall_pro`, `gohighlevel`, ...). |
Idempotency-Key header required idempotency-key-required HTTP 400 | POST or PATCH was called without an `Idempotency-Key` header. The header is required on every state-changing request. | Generate a fresh UUID per logical operation and send it as `Idempotency-Key: <uuid>`. Retries with the same key + body return the original result. |
Idempotency-Key too long idempotency-key-too-long HTTP 400 | The `Idempotency-Key` header exceeds 255 characters. | Use a UUID (36 chars) or a short opaque token. Keys longer than 255 bytes are rejected to bound storage. |
Idempotency-Key conflict idempotency-conflict HTTP 422 | The same `Idempotency-Key` was reused with a different request body. The first body is held for 24 hours; a second request with that key must match byte-for-byte. | Generate a new `Idempotency-Key` for the changed request. Reuse the original key only when retrying the exact same body. |
Plan required plan-required HTTP 403 | The endpoint requires a Pro+ plan and the API key belongs to a Starter workspace. `/v1/events` is the current Pro+ gate. | Upgrade in the dashboard under Billing. The same data is available in the in-app Events view on every plan. |
Rate limit exceeded rate-limit-exceeded HTTP 429 | Per-key rate limit was hit. The `RateLimit-Limit`, `RateLimit-Remaining`, and `RateLimit-Reset` response headers describe the window. | Back off until `RateLimit-Reset` seconds have passed, then retry. Honor `Retry-After` on 429s. For sustained throughput, contact us — we can lift the limit per key. |
Test-mode key not available test-mode-not-available HTTP 501 | A key with the `lr_test_` prefix was used. The `lr_test_` namespace is reserved for a future test-mode tier that has not shipped. | Use a live key (`lr_live_<rest>`) from your workspace. Test-mode arrives in a future release; this slug is the forward-compat placeholder. |
Unsafe outbound URL unsafe-url HTTP 422 | A destination config (webhook_url, callback_url, ...) points at a private network, an unsupported scheme, or otherwise fails the safe-outbound-URL check. The check runs at write time AND at delivery time. | Use a public HTTPS URL. Private IPs (10.x, 192.168.x, 127.x, etc.), link-local addresses, and non-HTTP(S) schemes are all rejected. |
Internal server error internal-error HTTP 500 | The API hit an unexpected exception. The `request_id` field in the response is the key to look up the request in logs. | Retry with backoff. If it persists, email support@leadrails.dev with the `request_id` — that's the fastest path to a root cause. |
Versioning
The type URLs are stable identifiers and will not change
for the v1 surface. New problem types may be added; existing slugs
will not be repurposed. If a slug is ever retired, this page will
keep redirecting to a successor and the API will emit the successor
going forward.