Send a finished conversation
const url = 'https://example.com/MIDWATER_BASE_URL/v1/conversations';const options = { method: 'POST', headers: {Authorization: 'Bearer <token>', 'Content-Type': 'application/json'}, body: '{"external_id":"call_8f2a91","channel":"voice","started_at":"2026-10-05T14:02:11Z","ended_at":"2026-10-05T14:06:40Z","ended_by":"caller","agent":{"id":"front-desk","name":"Front desk","version":"1.0.0"},"transcript":[{"speaker":"agent","text":"Thanks for calling, this call may be recorded. How can I help?","start_ms":0,"end_ms":2400},{"speaker":"user","text":"I need to move my appointment to Thursday.","start_ms":2900,"end_ms":5100,"asr_confidence":0.93},{"speaker":"agent","text":"Sure, I have Thursday at 10 AM. Does that work?","start_ms":5600,"end_ms":8200},{"speaker":"user","text":"Yes, that is perfect.","start_ms":8700,"end_ms":9900,"asr_confidence":0.96},{"speaker":"agent","text":"Great, you are all set for Thursday at 10 AM.","start_ms":61500,"end_ms":64000}],"events":[{"type":"tool_call","name":"reschedule_appointment","status":"success","at_ms":61000}],"metadata":{"language":"en"}}'};
try { const response = await fetch(url, options); const data = await response.json(); console.log(data);} catch (error) { console.error(error);}curl --request POST \ --url https://example.com/MIDWATER_BASE_URL/v1/conversations \ --header 'Authorization: Bearer <token>' \ --header 'Content-Type: application/json' \ --data '{ "external_id": "call_8f2a91", "channel": "voice", "started_at": "2026-10-05T14:02:11Z", "ended_at": "2026-10-05T14:06:40Z", "ended_by": "caller", "agent": { "id": "front-desk", "name": "Front desk", "version": "1.0.0" }, "transcript": [ { "speaker": "agent", "text": "Thanks for calling, this call may be recorded. How can I help?", "start_ms": 0, "end_ms": 2400 }, { "speaker": "user", "text": "I need to move my appointment to Thursday.", "start_ms": 2900, "end_ms": 5100, "asr_confidence": 0.93 }, { "speaker": "agent", "text": "Sure, I have Thursday at 10 AM. Does that work?", "start_ms": 5600, "end_ms": 8200 }, { "speaker": "user", "text": "Yes, that is perfect.", "start_ms": 8700, "end_ms": 9900, "asr_confidence": 0.96 }, { "speaker": "agent", "text": "Great, you are all set for Thursday at 10 AM.", "start_ms": 61500, "end_ms": 64000 } ], "events": [ { "type": "tool_call", "name": "reschedule_appointment", "status": "success", "at_ms": 61000 } ], "metadata": { "language": "en" } }'Stores a finished call or chat and queues it for scoring. Answers 202 with status: "queued";
read the result with GET /v1/conversations/{id} or wait for the conversation.evaluated webhook.
Sending an external_id that already exists in this environment answers 200 with the existing
conversation and duplicate: true; nothing new is created.
Send an Idempotency-Key header to make retries safe: a repeated key in the same environment
answers with the first response and Idempotent-Replayed: true.
A conversation without agent.id goes to the project’s Default agent (ID default). A new
agent.id creates the agent the first time it’s seen.
Authorizations
Section titled “Authorizations”Parameters
Section titled “Parameters”Header Parameters
Section titled “Header Parameters”Any string unique to this request, for example your external_id. A repeated key in the same environment
answers with the first response and Idempotent-Replayed: true. Planned (x-status: planned): keys
expire after 24 hours, and reusing a key with a different body answers 409 idempotency_conflict.
Today keys don’t expire and bodies aren’t compared.
Request Bodyrequired
Section titled “Request Bodyrequired”object
Your ID for this call or chat. Unique per environment.
Defaults to the time Midwater received it.
Who ended the conversation. timeout is valid for chat only.
The AI agent that handled the conversation. Omit it to use the project’s Default agent.
object
A letter or digit, then up to 127 letters, digits, ., _, : or -. No /, so IDs never need URL-encoding.
Shown in Midwater; used when the agent is first created.
Your agent’s release, e.g. 3.2.0. Releases are marked on the agent’s trend.
Files the agent into this group (created if needed) the first time the agent is seen without one. Ignored once someone places the agent in the app.
object
A letter or digit, then up to 127 letters, digits, ., _, : or -. No /, so IDs never need URL-encoding.
Every turn, in order.
object
agent is your AI agent, user the caller or chat user, human_agent a person who took over.
Offset from the start of the conversation.
Speech-recognition confidence for this turn (voice).
Something your agent did: a tool call, a transfer, a handoff. Extra properties are kept. The built-in booking checks turn on the first time a tool_call whose name contains book or schedul arrives. A transfer or handoff event marks the conversation as handed to a person.
object
Where a transfer went.
Anything else you want kept with the conversation.
object
Examples
A finished voice call
{ "external_id": "call_8f2a91", "channel": "voice", "started_at": "2026-10-05T14:02:11Z", "ended_at": "2026-10-05T14:06:40Z", "ended_by": "caller", "agent": { "id": "front-desk", "name": "Front desk", "version": "1.0.0" }, "transcript": [ { "speaker": "agent", "text": "Thanks for calling, this call may be recorded. How can I help?", "start_ms": 0, "end_ms": 2400 }, { "speaker": "user", "text": "I need to move my appointment to Thursday.", "start_ms": 2900, "end_ms": 5100, "asr_confidence": 0.93 }, { "speaker": "agent", "text": "Sure, I have Thursday at 10 AM. Does that work?", "start_ms": 5600, "end_ms": 8200 }, { "speaker": "user", "text": "Yes, that is perfect.", "start_ms": 8700, "end_ms": 9900, "asr_confidence": 0.96 }, { "speaker": "agent", "text": "Great, you are all set for Thursday at 10 AM.", "start_ms": 61500, "end_ms": 64000 } ], "events": [ { "type": "tool_call", "name": "reschedule_appointment", "status": "success", "at_ms": 61000 } ], "metadata": { "language": "en" }}Responses
Section titled “Responses”A conversation with this external_id already exists in this environment; it is returned instead of a duplicate.
object
Midwater’s conversation ID.
Scoring status. failed means scoring didn’t finish after Midwater’s automatic retries (five attempts in all); it can be re-run from the app.
Present and true when the external_id already existed.
Example
{ "id": "cmv0187nm005po3016rag4dcg", "status": "done", "duplicate": true}Headers
Section titled “Headers”true when this response is a replay of an earlier request with the same Idempotency-Key.
Example
req_8Jx2kQ4mT9The request’s ID, also in error bodies as error.request_id. Quote it when contacting support.
Accepted and queued for scoring.
object
Midwater’s conversation ID.
Scoring status. failed means scoring didn’t finish after Midwater’s automatic retries (five attempts in all); it can be re-run from the app.
Present and true when the external_id already existed.
Example
{ "id": "cmv0187nm005po3016rag4dcg", "status": "queued"}Headers
Section titled “Headers”true when this response is a replay of an earlier request with the same Idempotency-Key.
Example
req_8Jx2kQ4mT9The request’s ID, also in error bodies as error.request_id. Quote it when contacting support.
The body isn’t valid JSON.
object
object
Stable machine-readable type. Each has a set status: invalid_json 400, authentication_error 401,
permission_denied 403, not_found 404, method_not_allowed 405, idempotency_conflict 409, payload_too_large 413,
validation_error 422, rate_limited 429, server_error 500, service_unavailable 503.
Types not yet returned are planned. Treat a type you don’t know by its status class.
Human-readable; may change.
Validation errors by dotted path, e.g. transcript.0.speaker. _root for the body as a whole.
object
The request’s ID, the same as the Midwater-Request-Id header.
Example
{ "error": { "type": "invalid_json", "message": "Body must be valid JSON" }}Missing, malformed, unknown or revoked API key.
object
object
Stable machine-readable type. Each has a set status: invalid_json 400, authentication_error 401,
permission_denied 403, not_found 404, method_not_allowed 405, idempotency_conflict 409, payload_too_large 413,
validation_error 422, rate_limited 429, server_error 500, service_unavailable 503.
Types not yet returned are planned. Treat a type you don’t know by its status class.
Human-readable; may change.
Validation errors by dotted path, e.g. transcript.0.speaker. _root for the body as a whole.
object
The request’s ID, the same as the Midwater-Request-Id header.
Example
{ "error": { "type": "authentication_error", "message": "Missing or invalid API key. Use Authorization: Bearer mw_test_… or mw_live_…" }}The key is valid but not allowed to do this.
object
object
Stable machine-readable type. Each has a set status: invalid_json 400, authentication_error 401,
permission_denied 403, not_found 404, method_not_allowed 405, idempotency_conflict 409, payload_too_large 413,
validation_error 422, rate_limited 429, server_error 500, service_unavailable 503.
Types not yet returned are planned. Treat a type you don’t know by its status class.
Human-readable; may change.
Validation errors by dotted path, e.g. transcript.0.speaker. _root for the body as a whole.
object
The request’s ID, the same as the Midwater-Request-Id header.
Example
{ "error": { "type": "permission_denied", "message": "This key can't do that", "request_id": "req_8Jx2kQ4mT9" }}The path exists but doesn’t support this method. Allow lists the methods it does support.
object
object
Stable machine-readable type. Each has a set status: invalid_json 400, authentication_error 401,
permission_denied 403, not_found 404, method_not_allowed 405, idempotency_conflict 409, payload_too_large 413,
validation_error 422, rate_limited 429, server_error 500, service_unavailable 503.
Types not yet returned are planned. Treat a type you don’t know by its status class.
Human-readable; may change.
Validation errors by dotted path, e.g. transcript.0.speaker. _root for the body as a whole.
object
The request’s ID, the same as the Midwater-Request-Id header.
Example
{ "error": { "type": "method_not_allowed", "message": "GET isn't supported here; use POST", "request_id": "req_8Jx2kQ4mT9" }}Headers
Section titled “Headers”Example
POSTThe methods the path supports, e.g. POST.
The Idempotency-Key was already used with a different body.
object
object
Stable machine-readable type. Each has a set status: invalid_json 400, authentication_error 401,
permission_denied 403, not_found 404, method_not_allowed 405, idempotency_conflict 409, payload_too_large 413,
validation_error 422, rate_limited 429, server_error 500, service_unavailable 503.
Types not yet returned are planned. Treat a type you don’t know by its status class.
Human-readable; may change.
Validation errors by dotted path, e.g. transcript.0.speaker. _root for the body as a whole.
object
The request’s ID, the same as the Midwater-Request-Id header.
Example
{ "error": { "type": "idempotency_conflict", "message": "This Idempotency-Key was used with a different body", "request_id": "req_8Jx2kQ4mT9" }}The body is larger than the limit. The limit is published when it’s set.
object
object
Stable machine-readable type. Each has a set status: invalid_json 400, authentication_error 401,
permission_denied 403, not_found 404, method_not_allowed 405, idempotency_conflict 409, payload_too_large 413,
validation_error 422, rate_limited 429, server_error 500, service_unavailable 503.
Types not yet returned are planned. Treat a type you don’t know by its status class.
Human-readable; may change.
Validation errors by dotted path, e.g. transcript.0.speaker. _root for the body as a whole.
object
The request’s ID, the same as the Midwater-Request-Id header.
Example
{ "error": { "type": "payload_too_large", "message": "The request body is too large", "request_id": "req_8Jx2kQ4mT9" }}The body is JSON but doesn’t match the schema. fields maps each dotted path to its messages.
object
object
Stable machine-readable type. Each has a set status: invalid_json 400, authentication_error 401,
permission_denied 403, not_found 404, method_not_allowed 405, idempotency_conflict 409, payload_too_large 413,
validation_error 422, rate_limited 429, server_error 500, service_unavailable 503.
Types not yet returned are planned. Treat a type you don’t know by its status class.
Human-readable; may change.
Validation errors by dotted path, e.g. transcript.0.speaker. _root for the body as a whole.
object
The request’s ID, the same as the Midwater-Request-Id header.
Example
{ "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" ] } }}Too many requests. Wait Retry-After seconds. The limits are published when they’re set.
object
object
Stable machine-readable type. Each has a set status: invalid_json 400, authentication_error 401,
permission_denied 403, not_found 404, method_not_allowed 405, idempotency_conflict 409, payload_too_large 413,
validation_error 422, rate_limited 429, server_error 500, service_unavailable 503.
Types not yet returned are planned. Treat a type you don’t know by its status class.
Human-readable; may change.
Validation errors by dotted path, e.g. transcript.0.speaker. _root for the body as a whole.
object
The request’s ID, the same as the Midwater-Request-Id header.
Example
{ "error": { "type": "rate_limited", "message": "Too many requests", "request_id": "req_8Jx2kQ4mT9" }}Headers
Section titled “Headers”Seconds to wait before retrying.
Requests allowed in the current window. The numbers are published when the limits are set.
Requests left in the current window.
Seconds until the window resets.
Something went wrong on Midwater’s side. Safe to retry with the same Idempotency-Key.
object
object
Stable machine-readable type. Each has a set status: invalid_json 400, authentication_error 401,
permission_denied 403, not_found 404, method_not_allowed 405, idempotency_conflict 409, payload_too_large 413,
validation_error 422, rate_limited 429, server_error 500, service_unavailable 503.
Types not yet returned are planned. Treat a type you don’t know by its status class.
Human-readable; may change.
Validation errors by dotted path, e.g. transcript.0.speaker. _root for the body as a whole.
object
The request’s ID, the same as the Midwater-Request-Id header.
Example
{ "error": { "type": "server_error", "message": "Something went wrong on our side" }}Midwater is briefly unavailable. Retry with backoff, honouring Retry-After when present.
object
object
Stable machine-readable type. Each has a set status: invalid_json 400, authentication_error 401,
permission_denied 403, not_found 404, method_not_allowed 405, idempotency_conflict 409, payload_too_large 413,
validation_error 422, rate_limited 429, server_error 500, service_unavailable 503.
Types not yet returned are planned. Treat a type you don’t know by its status class.
Human-readable; may change.
Validation errors by dotted path, e.g. transcript.0.speaker. _root for the body as a whole.
object
The request’s ID, the same as the Midwater-Request-Id header.
Example
{ "error": { "type": "service_unavailable", "message": "Midwater is briefly unavailable", "request_id": "req_8Jx2kQ4mT9" }}Headers
Section titled “Headers”Seconds to wait before retrying.