---
title: "Resolve Assistant Turn Approval"
method: POST
path: "/api/v1/assistant-threads/{thread_id}/turns/{turn_id}/approval"
tags: ["authed"]
---

# Resolve Assistant Turn Approval

`POST /api/v1/assistant-threads/{thread_id}/turns/{turn_id}/approval`

## Request

- Base URL: `https://api.teaflask.com` (the document declares no server; this is the origin it was published from)
- URL: `https://api.teaflask.com/api/v1/assistant-threads/{thread_id}/turns/{turn_id}/approval`
- Auth: none declared

Answer a paused turn's pending approvals: the answers land as
durable rows, then the thread's ConversationWorkflow gets the
contentless interrupt_answered wake and routes them — couriered to
a live pause over the turn's unchanged signals, or consumed by the
resume it opens for a parked turn (its wait expired), which
continues the persisted conversation under the turn's pinned agent
revision. An answer already past this door answers a receipt
(already_consumed): the turn's own once a resume consumed it, or a
coworker's once its row left paused with the decision recorded —
however it left, or paused again at a newer round. Interrupt ids come
from the thread's approval_requested events and are deliberately
unvalidated here: an id the pause never raised satisfies nothing.

## Path parameters

- `thread_id` string, uuid, required
- `turn_id` string, uuid, required

## Request body

- ResolveTurnApprovalRequest
  - `approvals` TurnApprovalDecision[], required
    - `interrupt_id` string, required
    - `approved` boolean, required
    - `feedback` string, nullable

## Response `200`

Successful Response

- ResolveTurnApprovalResponse
  - `delivery` 'signaled' | 'workflow_restarted' | 'already_consumed', required
  - `turn` AssistantTurnResponse, required
    - `id` string, uuid, required
    - `thread_id` string, uuid, required
    - `user_message` string, required
    - `kind` 'delivery', required
    - `attachments` TurnAttachmentResponse[]
      - `id` string, uuid, required: The attachment's id.
      - `kind` 'image' | 'document' | 'video' | 'audio' | 'file', required: The assistant_attachments kind CHECK's closed set.
      - `format` string, nullable, required: The file's format, such as png or pdf; null when unknown.
      - `filename` string, required: The file's name as uploaded.
      - `byte_size` integer, required: The file's size in bytes.
    - `status` 'queued' | 'working' | 'awaiting_input' | 'succeeded' | 'failed' | 'parked' | 'superseded' | 'stopped', required
    - `error` string, nullable, required
    - `failure_reason` TurnFailureReason: Why the exchange failed: what kind of failure it was, and its facts.
      - `kind` 'no_backup_outage' | 'own_key_throttled' | 'own_key_outage' | 'own_key_declined' | 'own_key_required' | 'own_key_verifying' | 'backup_too_small' | 'summary_unavailable' | 'unfunded' | 'pocket_change_cap' | 'empty_balance' | 'model_contract_fault' | 'model_content_refused' | 'model_context_overflow' | 'model_provider_throttled' | 'subscription_unfunded' | 'subscription_unservable' | 'subscription_connect_required' | 'not_configured' | 'worker_lost' | 'runaway' | 'own_key_out_of_credit' | 'own_key_limited' | 'model_unlisted' | 'open_models_unavailable' | 'open_models_switched_off' | 'own_account_blocks_model', required: What kind of failure ended the exchange.
      - `detail` TurnFailureDetail: The facts behind the failure; each kind carries only its own.
        - `model_id` string, nullable: The model that was answering.
        - `provider` 'anthropic' | 'openai' | 'openrouter': Whose API a model call dials — the dimension BYOK keys, probes, bounce classification, and platform-key selection all key on. The vocabulary is append-only and each new value arrives with its integration (probe + classifier + key-shape rule + a platform key unless BYOK-only), never ahead of it.
        - `funding_source` 'balance' | 'byok' | 'end_user_subscription': Which pocket a model call bills against: the org's prepaid balance, the org's BYOK key, or an identified visitor's connected subscription. Per call, not per turn — one turn's rows may mix pockets. Closed partition under the llm_call_usage CHECK; the deduction paths drain the balance only for BALANCE rows. Complimentary money is BALANCE spend.
        - `which` 'message' | 'conversation', nullable: For a request too long for the model: whether the message alone or the whole conversation was too long.
        - `cause` 'tokens' | 'bytes' | 'summary_rejected', nullable: For a request too long for the model: what ran out.
        - `window_tokens` integer, nullable: How many tokens the model can read at once.
        - `plan_lapse` 'quota' | 'auth': Why a connected plan stopped paying: its usage limit, or its sign-in.
        - `balance_empty` boolean, nullable: For a declined API key: true when the balance was empty too, so neither could pay.
        - `welcome_credit_locked` boolean, nullable: For an empty balance: true when the organization had never added a card, so its welcome credit was still locked.
        - `redo_attempted` boolean, nullable: For an assistant that stopped unexpectedly: true when the step was already started again once on a fresh machine before it was given up.
        - `writer_prefix` string, nullable: For an assistant that stopped unexpectedly: the lost step's run and step ids, the prefix every write it made is labelled with — for support to find its trail.
        - `resets_at` string, date-time, nullable: When the key's spending limit resets.
    - `workflow_id` string, required
    - `run_id` string, nullable, required
    - `pending_interrupt_ids` string[]
    - `awaiting_round` integer, nullable
    - `pending_approvals` ApprovalRequestedMarker[]
      - `interrupt_id` string, required
      - `tool_name` string, nullable
      - `tool_args` object
      - `tool_input_schema` object, nullable
      - `tool_output_schema` object, nullable
      - `prompt` string, required
      - `tool_call_id` string, nullable
      - `round` integer
      - `gated` boolean
      - `trust_available` boolean
    - `pending_approval_displays` object
    - `unservable_interrupt_ids` string[]
    - `pending_coworker_executions` CoworkerExecutionRequestResponse[]
      - `ordinal` integer, required
      - `label` string, required
      - `child_session_id` string, required
      - `round` integer, required
      - `expires_at` string, date-time, required
      - `parked` boolean, required
      - `card` ToolExecutionRequestedMarker, required: The tool_execution_requested CUSTOM event's value payload. The tool_call_id anchors the card to the tool-call events already in the log; round is the loop round that paused, as on the approval card.
        - `interrupt_id` string, required
        - `tool_call_id` string, nullable
        - `tool_name` string, required
        - `round` integer
        - `request` ToolExecutionRequestPayload, required: The request half of a tool_execution_requested marker. kind is an open, additive vocabulary — clients ignore kinds they don't recognize. expires_at is advisory (the wait window that parks the turn rules).
          - `kind` string, required
          - `intent` object, nullable
          - `action` object, nullable
          - `expires_at` string, date-time, nullable
    - `pending_coworker_approvals` CoworkerApprovalRequestResponse[]
      - `ordinal` integer, required
      - `label` string, required
      - `child_session_id` string, required
      - `round` integer, required
      - `expires_at` string, date-time, required
      - `parked` boolean, required
      - `card` ApprovalRequestedMarker, required: The approval_requested CUSTOM event's value payload. The tool fields mirror the pending approval's best-effort shape (absent when no tool call was stopped); tool_input_schema / tool_output_schema are the paused tool's registered JSON schemas (a tool view reads the call without the catalog). round is the loop round that paused — what a fallback restart resumes one past. gated: the locked action-approval gate raised this pause; trust_available: the card may offer approve-and-stop-asking, scoped to the asker's own session (org hitl with trust enabled, never the gate).
        - `interrupt_id` string, required
        - `tool_name` string, nullable
        - `tool_args` object
        - `tool_input_schema` object, nullable
        - `tool_output_schema` object, nullable
        - `prompt` string, required
        - `tool_call_id` string, nullable
        - `round` integer
        - `gated` boolean
        - `trust_available` boolean
    - `agent_revision_id` string, uuid, nullable, required
    - `created_at` string, date-time, required
    - `updated_at` string, date-time, required

## Other responses

- `401`: Unauthorized
- `403`: Forbidden
- `404`: Not found
- `405`: Method not allowed
- `409`: The turn is not awaiting an approval
- `422`: Validation error
- `500`: Internal server error
- `503`: Orchestration unavailable

---

[API](https://skmtc.dev/teaflask/apis/teaflask-api.md) - [All operations](https://skmtc.dev/teaflask/apis/teaflask-api/llms.txt) - [OpenAPI document](https://skmtc.dev/teaflask/apis/teaflask-api/revisions/d23e9286b847?raw)
