Errors & limits
Error codes, rate limits, idempotency, and retry guidance.
Error format
All errors return JSON with a stable, machine-readable code:
{
"error": "Customer not found",
"code": "NOT_FOUND"
}Validation failures (422) add a details object describing the offending fields.
Plan-limit errors may add an upgradeUrl.
Error codes
| HTTP | code | Meaning |
|---|---|---|
401 | UNAUTHORIZED | Missing or invalid X-API-Key |
403 | FORBIDDEN | Outside the key's scope, a cross-org reference, or the key's agent is paused or archived |
403 | PLAN_LIMIT_REACHED | A plan limit (customer count, API calls) was hit |
404 | NOT_FOUND | Customer, conversation, or memory does not exist |
409 | CONFLICT | Unique constraint clash (e.g. duplicate email, phone, or external ID) |
409 | IDEMPOTENCY_IN_PROGRESS | A capture with the same idempotencyKey is still running |
409 | IDEMPOTENCY_KEY_REUSED | The idempotencyKey was already used for a capture with a different payload |
409 | ERASURE_EXPORT_SOURCES_UNKNOWN | Customer deletion is blocked by an unexpired export with unknown sources |
409 | ERASURE_MERGE_LINEAGE_AMBIGUOUS | Customer deletion is blocked by ambiguous merge history |
409 | ERASURE_GRAPH_SOURCES_UNKNOWN | Customer deletion is blocked by shared graph records with incomplete ownership |
409 | ERASURE_ANALYTICS_SOURCES_UNKNOWN | Customer deletion is blocked by analytics history with unknown sources |
422 | VALIDATION_ERROR | Body or query failed schema validation; see details |
429 | RATE_LIMITED | Rate limit exceeded; back off and retry |
503 | SERVICE_UNAVAILABLE | A backing service is temporarily unavailable; retry with backoff |
503 | DATABASE_UNAVAILABLE | Datastore offline; retry with backoff |
500 | INTERNAL_ERROR | Unexpected server error |
The ERASURE_* conflicts leave the customer's data in place for operator review — they
are not transient, so don't retry them automatically. See
Customers.
Rate limits
Limits are per-minute. Responses carry X-RateLimit-Limit and
X-RateLimit-Remaining. Exceeding a limit returns 429 RATE_LIMITED with a
Retry-After header.
Capture scales with your plan, and each agent gets its own budget — a busy support agent can't eat into what your sales agent has left. Both run at the full rate for your plan:
| Plan | Capture limit, per agent |
|---|---|
| Trial | 100 / min |
| Starter | 600 / min |
| Growth | 1,000 / min |
| Pro | 1,200 / min |
| Scale | 2,000 / min |
| Custom | Unlimited |
| Endpoint group | Limit |
|---|---|
POST /capture, memory ingest (/memories/ingest) | Your plan rate, per agent |
Recall — /customers/lookup, /customers/handoff, /customers/**/memories/lookup | 600 / min |
Live transfer routing — POST /handoff/route | 600 / min |
| Everything else (customers, conversations, CRUD) | 600 / min |
Public embed — /embed/handoff endpoints | 60 / min per IP |
Each capture call enqueues background AI work, so send a few conversation turns per call rather than one call per message. It costs less, goes further against your limit, and produces better memories than one-line captures.
Recall endpoints are deliberately generous (600/min) because agents call them on every turn.
Idempotency
POST /capture accepts an optional idempotencyKey (≤128 chars). Replaying the same
key with the same payload within 24 hours returns the original cached result instead
of re-processing. Reusing a key with a different payload returns
409 IDEMPOTENCY_KEY_REUSED.
If a request with that key is still in flight, you get 409 IDEMPOTENCY_IN_PROGRESS —
wait briefly and retry.
await client.capture(
[{ role: 'user', content: userMessage }],
{ customerId },
{ idempotencyKey: `turn-${conversationId}-${turnIndex}` },
)Use idempotency keys anywhere captures may be retried — webhook-driven flows, unreliable networks, or any fire-and-forget pattern that may replay.
Retry guidance
The TypeScript and Python SDKs retry 429, 5xx, and timed-out requests automatically
with a jittered, increasing delay (maxRetries / max_retries, default 3). They do not
read Retry-After. The Rust SDK does not retry automatically. For your own HTTP clients:
429— respect theRetry-Afterheader if present, otherwise back off exponentially503— transient service issue; retry with backoff409 IDEMPOTENCY_IN_PROGRESS— wait 1–2 seconds and retry with the same key4xx(other) — don't retry; fix the request
Phone number normalization
Phone numbers sent to any endpoint are normalized best-effort to E.164:
- 10 digits →
+1…(US/CA assumed) - 11 digits starting with
1→+… - Otherwise
+is prepended
Send E.164 directly (+14155550101) to avoid ambiguity.
What's next
- Authentication — API key scopes and security model
- Capture — idempotency in the capture flow
- AI agents — retry patterns in agentic tool calls