Skip to content

Send a finished conversation

POST
/v1/conversations
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.

Idempotency-Key
string
<= 255 characters

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.

Media typeapplication/json
object
external_id
required

Your ID for this call or chat. Unique per environment.

string
>= 1 characters <= 200 characters
channel
required
string
Allowed values: voice chat
started_at
string format: date-time
ended_at

Defaults to the time Midwater received it.

string format: date-time
ended_by

Who ended the conversation. timeout is valid for chat only.

string
Allowed values: agent caller user human_agent timeout system
agent

The AI agent that handled the conversation. Omit it to use the project’s Default agent.

object
id

A letter or digit, then up to 127 letters, digits, ., _, : or -. No /, so IDs never need URL-encoding.

string
/^[A-Za-z0-9][A-Za-z0-9._:-]{0,127}$/
name

Shown in Midwater; used when the agent is first created.

string
>= 1 characters <= 120 characters
version

Your agent’s release, e.g. 3.2.0. Releases are marked on the agent’s trend.

string
<= 60 characters
group

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
id
required

A letter or digit, then up to 127 letters, digits, ., _, : or -. No /, so IDs never need URL-encoding.

string
/^[A-Za-z0-9][A-Za-z0-9._:-]{0,127}$/
name
string
>= 1 characters <= 120 characters
transcript
required

Every turn, in order.

Array<object>
>= 1 items
object
speaker
required

agent is your AI agent, user the caller or chat user, human_agent a person who took over.

string
Allowed values: agent user human_agent
text
required
string
>= 1 characters
start_ms

Offset from the start of the conversation.

integer
end_ms
integer
asr_confidence

Speech-recognition confidence for this turn (voice).

number
<= 1
events
Array<object>

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
type
required
string
>= 1 characters
name
string
status
string
target

Where a transfer went.

string
at_ms
integer
key
additional properties
any
metadata

Anything else you want kept with the conversation.

object
key
additional properties
any

Examples

Exampledefault

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"
}
}

A conversation with this external_id already exists in this environment; it is returned instead of a duplicate.

Media typeapplication/json
object
id
required

Midwater’s conversation ID.

string
status
required

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.

string
Allowed values: queued evaluating done failed
duplicate

Present and true when the external_id already existed.

boolean

Example

{
"id": "cmv0187nm005po3016rag4dcg",
"status": "done",
"duplicate": true
}
Idempotent-Replayed
string
Allowed values: true

true when this response is a replay of an earlier request with the same Idempotency-Key.

Midwater-Request-Id
string
Example
req_8Jx2kQ4mT9

The request’s ID, also in error bodies as error.request_id. Quote it when contacting support.

Accepted and queued for scoring.

Media typeapplication/json
object
id
required

Midwater’s conversation ID.

string
status
required

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.

string
Allowed values: queued evaluating done failed
duplicate

Present and true when the external_id already existed.

boolean

Example

{
"id": "cmv0187nm005po3016rag4dcg",
"status": "queued"
}
Idempotent-Replayed
string
Allowed values: true

true when this response is a replay of an earlier request with the same Idempotency-Key.

Midwater-Request-Id
string
Example
req_8Jx2kQ4mT9

The request’s ID, also in error bodies as error.request_id. Quote it when contacting support.

The body isn’t valid JSON.

Media typeapplication/json
object
error
required
object
type
required

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.

string
Allowed values: invalid_json authentication_error permission_denied not_found method_not_allowed idempotency_conflict payload_too_large validation_error rate_limited server_error service_unavailable
message
required

Human-readable; may change.

string
fields

Validation errors by dotted path, e.g. transcript.0.speaker. _root for the body as a whole.

object
key
additional properties
Array<string>
request_id

The request’s ID, the same as the Midwater-Request-Id header.

string

Example

{
"error": {
"type": "invalid_json",
"message": "Body must be valid JSON"
}
}

Missing, malformed, unknown or revoked API key.

Media typeapplication/json
object
error
required
object
type
required

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.

string
Allowed values: invalid_json authentication_error permission_denied not_found method_not_allowed idempotency_conflict payload_too_large validation_error rate_limited server_error service_unavailable
message
required

Human-readable; may change.

string
fields

Validation errors by dotted path, e.g. transcript.0.speaker. _root for the body as a whole.

object
key
additional properties
Array<string>
request_id

The request’s ID, the same as the Midwater-Request-Id header.

string

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.

Media typeapplication/json
object
error
required
object
type
required

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.

string
Allowed values: invalid_json authentication_error permission_denied not_found method_not_allowed idempotency_conflict payload_too_large validation_error rate_limited server_error service_unavailable
message
required

Human-readable; may change.

string
fields

Validation errors by dotted path, e.g. transcript.0.speaker. _root for the body as a whole.

object
key
additional properties
Array<string>
request_id

The request’s ID, the same as the Midwater-Request-Id header.

string

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.

Media typeapplication/json
object
error
required
object
type
required

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.

string
Allowed values: invalid_json authentication_error permission_denied not_found method_not_allowed idempotency_conflict payload_too_large validation_error rate_limited server_error service_unavailable
message
required

Human-readable; may change.

string
fields

Validation errors by dotted path, e.g. transcript.0.speaker. _root for the body as a whole.

object
key
additional properties
Array<string>
request_id

The request’s ID, the same as the Midwater-Request-Id header.

string

Example

{
"error": {
"type": "method_not_allowed",
"message": "GET isn't supported here; use POST",
"request_id": "req_8Jx2kQ4mT9"
}
}
Allow
string
Example
POST

The methods the path supports, e.g. POST.

The Idempotency-Key was already used with a different body.

Media typeapplication/json
object
error
required
object
type
required

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.

string
Allowed values: invalid_json authentication_error permission_denied not_found method_not_allowed idempotency_conflict payload_too_large validation_error rate_limited server_error service_unavailable
message
required

Human-readable; may change.

string
fields

Validation errors by dotted path, e.g. transcript.0.speaker. _root for the body as a whole.

object
key
additional properties
Array<string>
request_id

The request’s ID, the same as the Midwater-Request-Id header.

string

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.

Media typeapplication/json
object
error
required
object
type
required

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.

string
Allowed values: invalid_json authentication_error permission_denied not_found method_not_allowed idempotency_conflict payload_too_large validation_error rate_limited server_error service_unavailable
message
required

Human-readable; may change.

string
fields

Validation errors by dotted path, e.g. transcript.0.speaker. _root for the body as a whole.

object
key
additional properties
Array<string>
request_id

The request’s ID, the same as the Midwater-Request-Id header.

string

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.

Media typeapplication/json
object
error
required
object
type
required

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.

string
Allowed values: invalid_json authentication_error permission_denied not_found method_not_allowed idempotency_conflict payload_too_large validation_error rate_limited server_error service_unavailable
message
required

Human-readable; may change.

string
fields

Validation errors by dotted path, e.g. transcript.0.speaker. _root for the body as a whole.

object
key
additional properties
Array<string>
request_id

The request’s ID, the same as the Midwater-Request-Id header.

string

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.

Media typeapplication/json
object
error
required
object
type
required

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.

string
Allowed values: invalid_json authentication_error permission_denied not_found method_not_allowed idempotency_conflict payload_too_large validation_error rate_limited server_error service_unavailable
message
required

Human-readable; may change.

string
fields

Validation errors by dotted path, e.g. transcript.0.speaker. _root for the body as a whole.

object
key
additional properties
Array<string>
request_id

The request’s ID, the same as the Midwater-Request-Id header.

string

Example

{
"error": {
"type": "rate_limited",
"message": "Too many requests",
"request_id": "req_8Jx2kQ4mT9"
}
}
Retry-After
integer

Seconds to wait before retrying.

RateLimit-Limit
integer

Requests allowed in the current window. The numbers are published when the limits are set.

RateLimit-Remaining
integer

Requests left in the current window.

RateLimit-Reset
integer

Seconds until the window resets.

Something went wrong on Midwater’s side. Safe to retry with the same Idempotency-Key.

Media typeapplication/json
object
error
required
object
type
required

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.

string
Allowed values: invalid_json authentication_error permission_denied not_found method_not_allowed idempotency_conflict payload_too_large validation_error rate_limited server_error service_unavailable
message
required

Human-readable; may change.

string
fields

Validation errors by dotted path, e.g. transcript.0.speaker. _root for the body as a whole.

object
key
additional properties
Array<string>
request_id

The request’s ID, the same as the Midwater-Request-Id header.

string

Example

{
"error": {
"type": "server_error",
"message": "Something went wrong on our side"
}
}

Midwater is briefly unavailable. Retry with backoff, honouring Retry-After when present.

Media typeapplication/json
object
error
required
object
type
required

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.

string
Allowed values: invalid_json authentication_error permission_denied not_found method_not_allowed idempotency_conflict payload_too_large validation_error rate_limited server_error service_unavailable
message
required

Human-readable; may change.

string
fields

Validation errors by dotted path, e.g. transcript.0.speaker. _root for the body as a whole.

object
key
additional properties
Array<string>
request_id

The request’s ID, the same as the Midwater-Request-Id header.

string

Example

{
"error": {
"type": "service_unavailable",
"message": "Midwater is briefly unavailable",
"request_id": "req_8Jx2kQ4mT9"
}
}
Retry-After
integer

Seconds to wait before retrying.