Documentation
Conversational Triage API
Basics
Errors and rate limits

Errors and rate limits

Error envelope

Every non-2xx HTTP response, and every streaming error event, uses one envelope:

JSON
{
  "error": {
    "code": "turn_already_active",
    "message": "Conversation 68d9a84d-711c-4a55-a9dd-da6363e58e38 is currently being processed."
  }
}

code is the stable, machine-readable identifier — branch on it. message is a human-readable explanation intended for logs, not for end users. The full list of codes and their HTTP statuses is in Error codes.

Validation failures

Request validation failures (422 / invalid_request) add a details array listing every offending field, while message stays a readable summary:

JSON
{
  "error": {
    "code": "invalid_request",
    "message": "Request validation failed with 2 errors: body.settings.language: Input should be a valid string; body.assessment.evidence[0].state: Input should be 'present', 'absent' or 'unknown'.",
    "details": [
      {
        "location": "body.settings.language",
        "message": "Input should be a valid string",
        "type": "string_type"
      },
      {
        "location": "body.assessment.evidence[0].state",
        "message": "Input should be 'present', 'absent' or 'unknown'",
        "type": "enum"
      }
    ]
  }
}

details is present whenever the failure was field validation, under a different code on each transport: invalid_request on HTTP, invalid_payload on WebSocket. Every other code — including 422 codes raised after the body parsed, such as unknown_evidence — keeps the two-field envelope.

On HTTP, location starts with the part of the request that failed (body, query, path, header, or cookie) and bracket-indexes array positions. On WebSocket it is a dot-joined path prefixed with the frame's event type, with array positions as plain segments — conversation.create.conversation.assessment rather than body.assessment. Branch on the transport before parsing it.

message summarizes at most the first five entries and appends ; and N more beyond that, while details always lists every failure. Submitted values are never echoed back.

ℹ️

type (string_type, missing, enum, …) is stable and safe to branch on when you map server-side validation onto your own form fields.

Errors on the streaming transports

The streaming transports deliver the same envelope inside an error event:

JSON
{ "type": "error", "error": { "code": "message_too_long", "message": "..." } }

A WebSocket connection stays open after an error event unless the server also closes the socket — see close codes. An HTTP NDJSON stream closes after a terminal turn event.

Errors you will handle most often

SituationCodeStatus
A turn is already being processed for this conversation (HTTP only).turn_already_active409
The user message is longer than max_message_length.message_too_long413
The interview already finished.conversation_completed409
The conversation was permanently terminated.conversation_terminated409
Seeded evidence refers to an unknown concept.unknown_evidence422
A summary was requested before a recommendation exists.not_ready_summary409

Rate limits

Rate-limited responses carry RateLimit-Limit, RateLimit-Remaining, RateLimit-Reset and — when applicable — Retry-After headers. Successful responses on rate-limited routes also carry the first three, so a client can back off before being throttled.

Exceeding the limit produces rate_limit_exceeded (429). On the WebSocket transport, a connection rejected by the rate limiter receives an error event with the same code and is closed with 1008 before the handshake completes.

Was this page helpful?