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",
  "details": {}
}

Validation failures (422) include a details object describing the offending fields.

Error codes

HTTPcodeMeaning
401UNAUTHORIZEDMissing or invalid X-API-Key
403FORBIDDENOutside the key's scope, or a cross-org reference
403PLAN_LIMIT_REACHEDA plan limit (customer count, API calls) was hit
404NOT_FOUNDCustomer, conversation, or memory does not exist
409CONFLICTUnique constraint clash (e.g. duplicate identifier)
409IDEMPOTENCY_IN_PROGRESSA capture with the same idempotencyKey is still running
422VALIDATION_ERRORBody or query failed schema validation; see details
429Rate limit exceeded; back off and retry
503DATABASE_UNAVAILABLEDatastore offline; retry with backoff
500INTERNAL_ERRORUnexpected server error

Rate limits

Limits are per-minute. Every response carries X-RateLimit-Limit and X-RateLimit-Remaining. Exceeding a limit returns 429 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:

PlanCapture limit, per agent
Trial100 / min
Starter600 / min
Growth1,000 / min
Pro1,200 / min
Scale2,000 / min
CustomUnlimited
Endpoint groupLimit
POST /capture, memory ingest (/memories/ingest)Your plan rate, per agent
Recall — /customers/lookup, /customers/handoff, /customers/**/memories/lookup600 / min
Everything else (customers, conversations, CRUD)600 / min
Public embed — GET /embed/handoff60 / min per IP

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 within 24 hours returns the original cached result instead of re-processing.

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 SDK retries 429 and 5xx responses automatically with exponential backoff (configurable via maxRetries, default 3). For your own HTTP clients:

  • 429 — respect the Retry-After header if present, otherwise back off exponentially
  • 503 — transient datastore issue; retry with backoff
  • 409 IDEMPOTENCY_IN_PROGRESS — wait 1–2 seconds and retry with the same key
  • 4xx (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