Skip to content
Transcriber Tests

POSThttps://core-api.heysadie.ai/turn-flags

callId messageIndex expectedText errorTags comment bankId success data flag id organizationId tenantId assistantId kind heardText audio startSeconds endSeconds evaluation controlForTurnId fullCallRunId tieBreak transcriberId reason label review calibration createdBy createdAt updatedAt liveTranscriber name evaluationProgress stage done total controls metadata timestamp version path error code message details field existingFlagId limit

Flag a caller line

Flag a caller line the call's live transcriber got wrong, with what the caller actually said. - The line's production attempt is the live transcriber's stored text, scored against `expectedText` at no cost. - With `bankId`, the flag joins that bank, the bank samples `controlLinesPerFlag` unflagged caller lines of the same call as controls, and the bank's candidates are queued on the flag and its controls. The returned turns stay `pending` until the candidates report: poll GET /turn-flags?callId=… or the bank's turns until `evaluation` leaves `pending`, showing `evaluationProgress`. A bank with no live candidate scores them at once. - Without `bankId`, the flag waits in no bank, `pending`, with no controls and nothing queued. Move it into a bank later with PATCH /turn-flags/:id; controls are sampled only for flags created in a bank. - A control sampled earlier on the same line is replaced by the flag. - No `Idempotency-Key` is honored here. A retried create gets 409 TURN_FLAG_EXISTS naming the flag the first attempt made, or 429 BACKLOG_FULL if the backlog filled meanwhile. Errors: 404 RESOURCE_NOT_FOUND when the call or `bankId` is not in the organization. 409 TURN_FLAG_EXISTS when the line already has a flag (`details.existingFlagId`). 422 LINE_NOT_FLAGGABLE when the line cannot be flagged (`details.reason`). 429 BACKLOG_FULL when the bank has candidates to queue and the organization already has more than 50 jobs waiting; nothing is written.

Try it in the API Explorer

Example request

Terminal
curl -X POST "https://core-api.heysadie.ai/turn-flags" \
  -H "Authorization: ApiKey YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "callId": "0199a1b2-7c3d-7e4f-8a5b-6c7d8e9f0a1b",
    "messageIndex": 4,
    "expectedText": "A table for four at 7:30 under Siobhan",
    "errorTags": [
      "name",
      "time"
    ],
    "comment": "Surname misheard",
    "bankId": "0199a1b2-1111-7222-8333-944455556666"
  }'

Authentication

Authenticate with an API key in the Authorization header using the ApiKey scheme. See Authentication for how keys work and where to get them.

Authorizationstringrequired

API key in the ApiKey scheme: ApiKey YOUR_API_KEY. Tenant keys authorize their own tenant; organization keys can act on any tenant in the organization.

X-Tenant-Idstring (uuid)optional

Required when authenticating with an organization key: it selects the tenant the request acts on. Omit with tenant keys.

Request

Body application/json · required

callIdstringrequired

A call of the organization

messageIndexstring (integer) | integerrequired

Index of a flaggable caller line, from GET /turn-flags/calls/:callId/lines

expectedTextstringrequired

What the caller actually said, word for word. 1 to 2000 characters, trimmed. Scoring ignores case and punctuation, but digits and number words stay distinct ("830" is not "eight thirty")

errorTagsstring[]required

What went wrong. Each `^[a-z_]{1,32}$`, at most 8 after duplicates collapse. GET /turn-flags/tags suggests some; any matching tag is accepted

commentstring | nulloptional

A free note, up to 2000 characters. Blank or null clears it

bankIdstring | nulloptional

A live bank of the organization for the flag and its controls to join; its candidates are queued on them. Null or absent leaves the flag in no bank, with no controls and nothing queued; PATCH /turn-flags/:id moves it later

Responses

201Created: the flag and its controls
successbooleanrequired

true on every 2xx

dataobjectoptional

The result

Show 2 child properties
flagobjectrequired

The new flag

Show 25 child properties
idstringrequired

Turn id

organizationIdstringrequired

The organization that owns the turn

tenantIdstringrequired

The tenant the call belongs to

callIdstringrequired

The call the line is from

assistantIdstring | nullrequired

The assistant that took the call. Null when the call names none

messageIndexstring (integer) | integerrequired

Index of the line in the call's messages, as GET /turn-flags/calls/:callId/lines lists it

bankIdstring | nullrequired

The bank the turn belongs to. Null while it is in no bank, and then no candidate replays it

kindenumrequired

flagged: a person flagged the line as wrong and typed what was said. control: an unflagged caller line of the same call, sampled with a flag, whose expected text is the live transcriber's own. full_call: a caller line replayed by a full-call run, listed under that run

Allowed values:"flagged""control""full_call"
heardTextstringrequired

What the live transcriber wrote for the line on the call, frozen when the turn was created

expectedTextstring | nullrequired

What the caller said, word for word: typed for a flag, the heard text for a control. A full-call turn carries the expected text of a flag on the same line, else null

errorTagsstring[]required

Tags on what went wrong, e.g. `name` or `spelling`. Empty on controls and full-call turns

commentstring | nullrequired

The flagger's note. Null when none

audioobjectrequired

The line's place in the call recording. Transcribers hear it with 0.75 s more on each side, so play `startSeconds - 0.75` to `endSeconds + 0.75`, clamped to the recording, to hear what they heard

Show 2 child properties
startSecondsnumberrequired

Where the line starts, in seconds from the recording start

endSecondsnumberrequired

Where the line ends, in seconds from the recording start

evaluationenumrequired

pending: candidate attempts are queued and not all reported, or the turn was created in no bank. scored: every transcriber its job asked for returned an attempt (a failed attempt counts), or it joined a bank that owed it no attempt. failed: its job ended without an attempt from every transcriber it asked for. A turn taken out of a bank keeps its evaluation, and a backfill does not move a scored turn back to pending: watch `evaluationProgress` for that work

Allowed values:"pending""scored""failed"
controlForTurnIdstring | nullrequired

On a control, the flag it was sampled for. Deleting or moving that flag deletes or moves the control. Null otherwise

fullCallRunIdstring | nullrequired

On a full-call turn, its run. Null otherwise

tieBreakobject | nullrequired

When two or more candidates match the expected text with different raw text (casing, punctuation), the one an LLM judge found fits the venue best. Null otherwise

Show 2 child properties
transcriberIdstringrequired

The candidate whose text the judge picked

reasonstringrequired

The judge's one-sentence reason

labelenum | nullrequired

On a full-call turn where the candidate differs from production, its label: decided by the expected-text matches where the line has expected text, else by the LLM judge. candidate_right: the candidate has what the caller said and production does not. production_right: production has it and the candidate does not. same_meaning: they differ in a way that does not matter. both_wrong: neither has it. Null otherwise or until the analysis labels it

Allowed values:"candidate_right""production_right""same_meaning""both_wrong"
reviewenum | nullrequired

On a full-call turn, a person's verdict from POST /full-call-runs/:id/reviews, in the same values as `label`. It counts only on differing turns, and outranks the label in `fixed` and `introduced` only where the line has no expected text. Null until reviewed

Allowed values:"candidate_right""production_right""same_meaning""both_wrong"
calibrationbooleanrequired

On a full-call turn, whether production is also replayed on it to measure replay against the live text. False otherwise

createdBystring | nullrequired

Who made it, as the creating request named them in an `X-On-Behalf-Of` header. Null when it sent none

createdAtstring (date-time) | string (date) | number | nullrequired

When it was created, UTC

updatedAtstring (date-time) | string (date) | number | nullrequired

When it last changed, UTC

liveTranscriberobject | nullrequired

The transcriber the turn's call ran on, recorded when the turn was created. Its stored text is the turn's production attempt. Null when unknown

Show 2 child properties
idstringrequired

Transcriber id, as GET /transcribers lists it

namestringrequired

The transcriber's display name

evaluationProgressobject | nullrequired

The newest open transcribe job holding the turn, for a progress bar. Null when no open job holds it (scored, failed or never queued), and on the POST /turn-flags response, which is written before its job is queued: read the turn again to see it

Show 3 child properties
stageenumrequired

waiting: the job is queued or dispatched and no worker has started it. scoring: a worker is running it

Allowed values:"waiting""scoring"
donestring (integer) | integerrequired

Transcriptions the job has finished, over every clip and transcriber it holds, not this turn's alone. 0 until the worker first reports

totalstring (integer) | integerrequired

Transcriptions the job holds in all

controlsobject[]required

The control turns sampled with it, by line. Empty without a bank

Show 25 item properties
idstringrequired

Turn id

organizationIdstringrequired

The organization that owns the turn

tenantIdstringrequired

The tenant the call belongs to

callIdstringrequired

The call the line is from

assistantIdstring | nullrequired

The assistant that took the call. Null when the call names none

messageIndexstring (integer) | integerrequired

Index of the line in the call's messages, as GET /turn-flags/calls/:callId/lines lists it

bankIdstring | nullrequired

The bank the turn belongs to. Null while it is in no bank, and then no candidate replays it

kindenumrequired

flagged: a person flagged the line as wrong and typed what was said. control: an unflagged caller line of the same call, sampled with a flag, whose expected text is the live transcriber's own. full_call: a caller line replayed by a full-call run, listed under that run

Allowed values:"flagged""control""full_call"
heardTextstringrequired

What the live transcriber wrote for the line on the call, frozen when the turn was created

expectedTextstring | nullrequired

What the caller said, word for word: typed for a flag, the heard text for a control. A full-call turn carries the expected text of a flag on the same line, else null

errorTagsstring[]required

Tags on what went wrong, e.g. `name` or `spelling`. Empty on controls and full-call turns

commentstring | nullrequired

The flagger's note. Null when none

audioobjectrequired

The line's place in the call recording. Transcribers hear it with 0.75 s more on each side, so play `startSeconds - 0.75` to `endSeconds + 0.75`, clamped to the recording, to hear what they heard

Show 2 child properties
startSecondsnumberrequired

Where the line starts, in seconds from the recording start

endSecondsnumberrequired

Where the line ends, in seconds from the recording start

evaluationenumrequired

pending: candidate attempts are queued and not all reported, or the turn was created in no bank. scored: every transcriber its job asked for returned an attempt (a failed attempt counts), or it joined a bank that owed it no attempt. failed: its job ended without an attempt from every transcriber it asked for. A turn taken out of a bank keeps its evaluation, and a backfill does not move a scored turn back to pending: watch `evaluationProgress` for that work

Allowed values:"pending""scored""failed"
controlForTurnIdstring | nullrequired

On a control, the flag it was sampled for. Deleting or moving that flag deletes or moves the control. Null otherwise

fullCallRunIdstring | nullrequired

On a full-call turn, its run. Null otherwise

tieBreakobject | nullrequired

When two or more candidates match the expected text with different raw text (casing, punctuation), the one an LLM judge found fits the venue best. Null otherwise

Show 2 child properties
transcriberIdstringrequired

The candidate whose text the judge picked

reasonstringrequired

The judge's one-sentence reason

labelenum | nullrequired

On a full-call turn where the candidate differs from production, its label: decided by the expected-text matches where the line has expected text, else by the LLM judge. candidate_right: the candidate has what the caller said and production does not. production_right: production has it and the candidate does not. same_meaning: they differ in a way that does not matter. both_wrong: neither has it. Null otherwise or until the analysis labels it

Allowed values:"candidate_right""production_right""same_meaning""both_wrong"
reviewenum | nullrequired

On a full-call turn, a person's verdict from POST /full-call-runs/:id/reviews, in the same values as `label`. It counts only on differing turns, and outranks the label in `fixed` and `introduced` only where the line has no expected text. Null until reviewed

Allowed values:"candidate_right""production_right""same_meaning""both_wrong"
calibrationbooleanrequired

On a full-call turn, whether production is also replayed on it to measure replay against the live text. False otherwise

createdBystring | nullrequired

Who made it, as the creating request named them in an `X-On-Behalf-Of` header. Null when it sent none

createdAtstring (date-time) | string (date) | number | nullrequired

When it was created, UTC

updatedAtstring (date-time) | string (date) | number | nullrequired

When it last changed, UTC

liveTranscriberobject | nullrequired

The transcriber the turn's call ran on, recorded when the turn was created. Its stored text is the turn's production attempt. Null when unknown

Show 2 child properties
idstringrequired

Transcriber id, as GET /transcribers lists it

namestringrequired

The transcriber's display name

evaluationProgressobject | nullrequired

The newest open transcribe job holding the turn, for a progress bar. Null when no open job holds it (scored, failed or never queued), and on the POST /turn-flags response, which is written before its job is queued: read the turn again to see it

Show 3 child properties
stageenumrequired

waiting: the job is queued or dispatched and no worker has started it. scoring: a worker is running it

Allowed values:"waiting""scoring"
donestring (integer) | integerrequired

Transcriptions the job has finished, over every clip and transcriber it holds, not this turn's alone. 0 until the worker first reports

totalstring (integer) | integerrequired

Transcriptions the job holds in all

metadataobjectoptional

Request metadata

Show 3 child properties
timestampstringrequired

When the server answered, ISO 8601 UTC

versionstringrequired

API version

pathstringoptional

The URL that was requested

400VALIDATION_ERROR: a path id, query parameter or body field broke a rule; `details` lists each `field` and its `message`. A body with a missing field or a JSON type the schema does not allow answers 400 with code INTERNAL_SERVER_ERROR and no `details` instead, so branch on the status
successbooleanrequired

false on every error

errorobjectoptional

What went wrong: a stable `code` to branch on and a human-readable `message`

Show 3 child properties
codeenumrequired

A parameter or body field broke a rule

Allowed values:"VALIDATION_ERROR"
messagestringrequired

Human-readable explanation. Do not parse it; branch on `code`

detailsobject[]optional

One entry per field that failed

Show 2 item properties
fieldstringrequired

The offending field

messagestringrequired

The rule it broke

metadataobjectoptional

Request metadata

Show 3 child properties
timestampstringrequired

When the server answered, ISO 8601 UTC

versionstringrequired

API version

pathstringoptional

The URL that was requested

401UNAUTHORIZED: no valid credentials, or a dashboard token that names no organization. Send `Authorization: ApiKey <organization API key>`
successbooleanrequired

false on every error

errorobjectoptional

What went wrong: a stable `code` to branch on and a human-readable `message`

Show 2 child properties
codeenumrequired

No valid credentials

Allowed values:"UNAUTHORIZED"
messagestringrequired

Human-readable explanation. Do not parse it; branch on `code`

metadataobjectoptional

Request metadata

Show 3 child properties
timestampstringrequired

When the server answered, ISO 8601 UTC

versionstringrequired

API version

pathstringoptional

The URL that was requested

403ORG_SCOPE_REQUIRED: transcriber testing is an organization feature. Use an organization API key without `X-Tenant-Id`, or a dashboard token scoped to the organization. A tenant API key, an organization key sent with `X-Tenant-Id` for one of its tenants, or a token carrying a tenant gets this 403. FORBIDDEN: an organization key sent `X-Tenant-Id` for a tenant outside its organization
successbooleanrequired

false on every error

errorobjectoptional

What went wrong: a stable `code` to branch on and a human-readable `message`

Show 2 child properties
codeenumrequired

ORG_SCOPE_REQUIRED: the credentials carry a tenant scope. FORBIDDEN: an organization key sent `X-Tenant-Id` for a tenant outside its organization

Allowed values:"ORG_SCOPE_REQUIRED""FORBIDDEN"
messagestringrequired

Human-readable explanation. Do not parse it; branch on `code`

metadataobjectoptional

Request metadata

Show 3 child properties
timestampstringrequired

When the server answered, ISO 8601 UTC

versionstringrequired

API version

pathstringoptional

The URL that was requested

404RESOURCE_NOT_FOUND: an id in the path, query or body names nothing in the caller's organization. `message` names what, e.g. "Transcriber bank not found"
successbooleanrequired

false on every error

errorobjectoptional

What went wrong: a stable `code` to branch on and a human-readable `message`

Show 2 child properties
codeenumrequired

An id names nothing the caller can see

Allowed values:"RESOURCE_NOT_FOUND"
messagestringrequired

Human-readable explanation. Do not parse it; branch on `code`

metadataobjectoptional

Request metadata

Show 3 child properties
timestampstringrequired

When the server answered, ISO 8601 UTC

versionstringrequired

API version

pathstringoptional

The URL that was requested

409TURN_FLAG_EXISTS: the line already has a live flag; edit it with PATCH /turn-flags/:id using `details.existingFlagId`
successbooleanrequired

false on every error

errorobjectoptional

What went wrong: a stable `code` to branch on and a human-readable `message`

Show 3 child properties
codeenumrequired

The line already has a flag

Allowed values:"TURN_FLAG_EXISTS"
messagestringrequired

Human-readable explanation. Do not parse it; branch on `code`

detailsobjectrequired

The flag in the way

Show 1 child properties
existingFlagIdstring | nullrequired

The line's live flag, to PATCH instead. Null if it was deleted meanwhile

metadataobjectoptional

Request metadata

Show 3 child properties
timestampstringrequired

When the server answered, ISO 8601 UTC

versionstringrequired

API version

pathstringoptional

The URL that was requested

422LINE_NOT_FLAGGABLE: the line cannot be flagged; `details.reason` says why, as `notFlaggableReason` does on GET /turn-flags/calls/:callId/lines
successbooleanrequired

false on every error

errorobjectoptional

What went wrong: a stable `code` to branch on and a human-readable `message`

Show 3 child properties
codeenumrequired

The line cannot be flagged

Allowed values:"LINE_NOT_FLAGGABLE"
messagestringrequired

Human-readable explanation. Do not parse it; branch on `code`

detailsobjectrequired

Why

Show 1 child properties
reasonenumrequired

Why a line cannot be flagged, the first that holds of: not_caller_line (an assistant line), no_recording (the call has no recording), transcriber_unknown (the call records no transcriber and its cost breakdown names none uniquely, so there is no live transcriber to score against), no_audio_bounds (the line has no start and end in the recording)

Allowed values:"not_caller_line""no_audio_bounds""no_recording""transcriber_unknown"
metadataobjectoptional

Request metadata

Show 3 child properties
timestampstringrequired

When the server answered, ISO 8601 UTC

versionstringrequired

API version

pathstringoptional

The URL that was requested

429BACKLOG_FULL: the organization has more than `details.limit` (50) transcriber jobs waiting. Nothing was written; retry once some finish
successbooleanrequired

false on every error

errorobjectoptional

What went wrong: a stable `code` to branch on and a human-readable `message`

Show 3 child properties
codeenumrequired

Too many jobs waiting

Allowed values:"BACKLOG_FULL"
messagestringrequired

Human-readable explanation. Do not parse it; branch on `code`

detailsobjectrequired

The limit hit

Show 1 child properties
limitstring (integer) | integerrequired

Pending jobs the organization may hold

metadataobjectoptional

Request metadata

Show 3 child properties
timestampstringrequired

When the server answered, ISO 8601 UTC

versionstringrequired

API version

pathstringoptional

The URL that was requested

500An unexpected server error
successbooleanrequired

false on every error

errorobjectoptional

What went wrong: a stable `code` to branch on and a human-readable `message`

Show 2 child properties
codeenumrequired

An unexpected server failure

Allowed values:"INTERNAL_SERVER_ERROR"
messagestringrequired

Human-readable explanation. Do not parse it; branch on `code`

metadataobjectoptional

Request metadata

Show 3 child properties
timestampstringrequired

When the server answered, ISO 8601 UTC

versionstringrequired

API version

pathstringoptional

The URL that was requested