Errors, retries and limits
Error format
Section titled “Error format”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.
Error types
Section titled “Error types”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.
Retrying safely
Section titled “Retrying safely”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.
Rate limits
Section titled “Rate limits”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.
Pagination
Section titled “Pagination”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.
Field limits
Section titled “Field limits”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 |