Skip to content

Errors, retries and limits

Every error from the API is JSON with the same shape:

{
"error": {
"type": "validation_error",
"message": "The conversation payload is invalid",
"fields": {
"channel": ["Invalid enum value. Expected 'voice' | 'chat', received 'fax'"],
"transcript": ["transcript needs at least one turn"]
}
}
}

Branch on type, which is stable, and on the status. message is for people and may change. fields appears on validation errors and maps each dotted path (transcript.0.speaker) to its messages.

Planned Every error body will also carry request_id, and every response a Midwater-Request-Id header with the same value. Quote it when you contact support.

Each type has one status. If you get a type you don’t know, handle it by its status class: 4xx means fix the request, 5xx means retry later.

Status type When Retry?
400 invalid_json The body isn’t valid JSON. No
401 authentication_error Missing, malformed, unknown or revoked key. No
403 permission_denied The key can’t do this. No Planned
404 not_found Not found in the key’s environment. No
405 method_not_allowed The path doesn’t support this method; the Allow header lists those it does. No Planned
409 idempotency_conflict The Idempotency-Key was used with a different body. No Planned
413 payload_too_large The body is over the size limit. No Planned
422 validation_error The body doesn’t match the schema; see fields. No
429 rate_limited Too many requests. Yes, after Retry-After Planned
500 server_error Something went wrong on Midwater’s side. Yes
503 service_unavailable Midwater is briefly unavailable. Yes, after Retry-After if present Planned

“Planned” types are agreed but not returned yet. Build for them now and your integration keeps working when they arrive.

Planned Every /v1 path will answer errors as JSON. Today, a path that doesn’t exist answers 404 and an unsupported method answers 405 without a JSON body; both will carry the error shape above, and 405 an Allow header. Endpoints that aren’t available yet will answer 501 with not_implemented and a link to these docs.

Retry network errors, 408, 429 and 5xx, at most 3 times, with exponential backoff (for example 0.5 s, 1 s, 2 s) plus jitter. When a Retry-After header is present, wait that many seconds instead. Retry only requests that are safe to repeat: GETs, and POST /v1/conversations with an Idempotency-Key (see Sending conversations). Feedback doesn’t honour idempotency keys yet, so don’t retry it blindly. The SDKs follow these rules for you.

Planned Requests over the limit will answer 429 with rate_limited, a Retry-After header, and RateLimit-Limit, RateLimit-Remaining and RateLimit-Reset headers. The numbers will be published here when they’re set. Until then, build for 429 anyway.

There are no list endpoints in v1, so nothing is paginated. When list endpoints arrive, they’ll be cursor-based: send limit and cursor, get back { "data": [...], "next_cursor": "…" }, and stop when next_cursor is null.

external_id 1 to 200 characters, unique per environment
agent.id, group.id 1 to 128 characters: letters, digits, ., _, :, -; starting with a letter or digit
agent.name, group.name 1 to 120 characters
agent.version Up to 60 characters
transcript At least one turn; each turn’s text at least one character
Feedback note Up to 1,000 characters