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.
Building an integration that sends lead events? Most
of these errors come from a hand-rolled client getting the headers,
idempotency key, or HMAC signature wrong. Don't hand-roll it — use
@leadrails/sdk, which computes every
required header, the canonical signing base string, and the
idempotency key for you, so the integration in your repo is a typed
function call. See the Server SDK page.
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. Learn more: Authentication → |
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. The @leadrails/sdk derives and sends this header for you. Learn more: Idempotency → |
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. Learn more: Idempotency → |
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. Learn more: Idempotency → |
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. Learn more: Rate limits → |
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. Learn more: Authentication → |
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. |
Schema validation failed schema-validation-failed HTTP 422 | A POST/PATCH body failed top-level schema validation. The specific field errors are returned in the `issues` extension member (each with a `path` and `message`). | Walk the `issues` array — each entry names the offending field path and why it failed. Correct those fields and resend with the same `Idempotency-Key`. |
Invalid destination config invalid-destination-config HTTP 422 | The `config` object on a destination create/update failed the adapter-specific schema for its `adapter_type` (e.g. a Slack destination missing `webhook_url`). The per-field problems are in the `issues` extension. | Check the `config` shape for the chosen `adapter_type` in the API reference, fix the fields named in `issues`, and resend. |
Test variable validation failed variable-validation-failed HTTP 422 | A test-send request supplied template variables that do not satisfy the destination template's variable contract (a required output is missing or a supplied value is the wrong shape). | Provide every variable the template declares, with values matching its contract. The `detail` and any `issues` extension name what was missing or invalid. |
Unsafe outbound URL unsafe-outbound-url HTTP 422 | A URL field inside a destination `config` failed the SSRF guard at write time — a defence-in-depth re-check on top of the adapter schema. The offending field and reason are in `detail`. | Use a public HTTPS endpoint. Private IP ranges (10.x, 192.168.x, 127.x), link-local addresses, and non-HTTP(S) schemes are rejected for every config URL field. |
Request in progress idempotency-in-progress HTTP 409 | A concurrent request with the same `Idempotency-Key` is still being processed. The first call has not finished, so the result is not yet available to replay. | Wait for the first request to complete, then retry with the same key to receive the original result. Do not fire concurrent requests that share an `Idempotency-Key`. Learn more: Idempotency → |
Route cap exceeded route-cap-exceeded HTTP 402 | Creating (or activating a paused) route would exceed the number of active routes your plan allows. The `cap`, `current`, `plan_slug`, and `upgrade_url` are returned as extension members. | Remove or pause an existing active route, or upgrade your plan via the `upgrade_url` in the response, then retry. |
Refresh rate-limited refresh-rate-limited HTTP 429 | A destination template-manifest refresh was requested again too soon. Refreshes are throttled per destination; the `retry_after_seconds` extension says how long to wait. | Wait `retry_after_seconds` (also reflected in `Retry-After`) before refreshing this destination's template manifest again. Learn more: Rate limits → |
Adapter does not support template refresh adapter-does-not-support-template-refresh HTTP 422 | A template-manifest refresh was requested for a destination whose `adapter_type` has no provider-side template manifest to refresh. | Only call refresh on adapters that expose a provider-side template manifest. For other adapter types there is nothing to refresh — manage their config directly. |
Service unavailable service-unavailable HTTP 503 | The API is temporarily unable to serve the request (e.g. a backing dependency is unhealthy or a server-side safety guard tripped). This is transient and not caused by your request body. | Retry with exponential backoff. If it persists, check the status page and email support@leadrails.dev with the `request_id`. |
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.