Questions and approvals API
Kheish exposes approvals and structured user questions as first-class suspended-run states. These endpoints do not create unrelated work. They resume runs that are waiting.Endpoint inventory
List pending user questions:GET /v1/questionsGET /v1/sessions/{session_id}/questions
POST /v1/sessions/{session_id}/approvalsPOST /v1/sessions/{session_id}/questionsPOST /v1/sessions/{session_id}/approval-runs
POST /v1/runs/{run_id}/approvalsPOST /v1/runs/{run_id}/questionsPOST /v1/runs/{run_id}/questions/{request_id}/cancel
Which endpoint should you use?
Use the session-scoped endpoints when you already operate in a session workflow and want the result projected back onto the session surface. Use the run-scoped endpoints when you already have a concrete suspendedrun_id and want a detached resume handle.
Behavior difference:
POST /v1/sessions/{session_id}/approvals- returns an updated
SessionView
- returns an updated
POST /v1/sessions/{session_id}/questions- returns an updated
SessionView
- returns an updated
POST /v1/sessions/{session_id}/approval-runs- returns
202 Acceptedplus a detachedRunView
- returns
POST /v1/runs/{run_id}/approvals- returns
202 Acceptedplus a detachedRunView
- returns
POST /v1/runs/{run_id}/questions- returns
202 Acceptedplus a detachedRunView
- returns
POST /v1/runs/{run_id}/questions/{request_id}/cancel- validates that
request_idis pending onrun_id, then returns the cancelled or declinedRunView
- validates that
List pending user questions
GET /v1/questions supports:
session_id
GET /v1/sessions/{session_id}/questions is the session-scoped equivalent.
The response is a list of PendingQuestionView objects:
session_idagent_idrun_idrun_kindrequester_agent_idrequester_session_idrequester_run_idrequester_tool_call_idrequester_project_idsrequester_channel_idsparent_project_idsparent_channel_idsrequest
parent_clarification runs. They let operators see which child run is blocked, where the question came from, and which project or channel contexts were active when the child escalated the clarification.
request is a UserQuestionRequest:
idtool_call_idquestionscreated_at_msexpires_at_ms
idheaderquestionoptionsmulti_select
Resolve approvals
Approval endpoints accept:ApprovalResolution supports:
request_idbehaviorallowdeny
updated_inputjustificationreason
updated_input when the approver wants to modify the pending tool input before allowing it.
Clients can also send the idempotency key through the Idempotency-Key header. Reusing the same key with an identical payload returns the same resumed run; reusing it with a different payload is rejected.
Resolve structured user questions
Question endpoints accept:UserQuestionResolution fields:
request_idanswersdeclinedjustification
question_idselected_option_idsfreeform_answer
declined to true and leave answers empty.
Clients can also send the idempotency key through the Idempotency-Key header. This is recommended for CLI and UI retries because question answers resume the original suspended run.
Cancel structured user questions
Cancel a specific pending question through the run-scoped cancel endpoint:POST /v1/runs/{run_id}/questions/{request_id}/cancel cancels the waiting run only when the request id still matches a pending question on that run. A stale or mistyped request_id is rejected and the run remains waiting.
For parent-clarification questions, the same endpoint records a declined clarification and delivers that declined answer back to the requesting child. The optional justification is included in the declined clarification payload.
questions cancel uses this request-scoped endpoint. The older run-level POST /v1/runs/{run_id}/cancel still cancels a run directly, but clients that are acting on a question should prefer the request-scoped question cancel endpoint.
Clients can also send the cancel idempotency key through the Idempotency-Key header. This is recommended when retrying a request-scoped cancel after network loss or double-submit. CLI retries with questions cancel --idempotency-key require --run-id, because a completed cancel removes the pending question from session-scoped discovery.
Question errors
Question endpoints returnapplication/problem+json with stable domain and code fields. Common codes:
| Status | Domain | Code | Meaning |
|---|---|---|---|
400 | questions | question_request_mismatch | The resolution or cancel request does not match the pending question request. |
400 | questions | question_option_not_found | An answer selected an option id that is not part of the question. |
400 | questions | question_answer_missing | A required structured question was not answered. |
400 | questions | question_duplicate_answer | The payload answered the same question more than once. |
400 | questions | question_duplicate_option | The payload selected the same option more than once. |
400 | questions | question_declined_with_answers | A declined resolution also supplied answers. |
400 | questions | question_single_select_violation | A single-select question received multiple selected options. |
400 | questions | question_answer_empty | An answer supplied neither selected options nor freeform text. |
400 | questions | question_unknown_answer | The payload included answers for questions not present in the request. |
409 | questions | question_state_conflict | The run is not waiting for a user question. |
409 | questions | question_expired | The question expired before the answer or cancel request arrived. |
409 | questions | question_resolution_conflict | A parent-clarification retry supplied a different answer after the first resolution was already durably claimed. |
409 | idempotency | idempotency_conflict | The idempotency key was reused with a different payload. |
Run states
These endpoints are relevant when a run enters one of these states:waiting_for_approvalwaiting_for_user_question
GET /v1/sessions/{session_id}GET /v1/runs/{run_id}
RunView:
pending_approval_idspending_approvalspending_question_idspending_questions
expires_at_msfor an absolute Unix timestamp in millisecondsexpires_after_msfor a relative timeout from question creation
GET /v1/runs/{run_id}/events and GET /v1/sessions/{session_id}/events include a structured parent_clarification_resolved event for parent-clarification answers.
Operational notes
- Approval resolution is batch-based: send every decision you want to resolve in the current request.
- Approval batches are atomic per run: invalid, duplicate, or stale request IDs reject the batch without resolving any approval in that run.
approvals allow-allandapprovals deny-allgroup work by run and submit one atomic batch per run; they are not one global transaction across multiple sessions or runs.- Session-scoped resume endpoints operate on the active waiting state for that session.
- Run-scoped resume endpoints are safer when multiple suspended runs could exist over time and the client wants to target one exact waiting run.
- CLI operators can answer with
questions answer --interactive, decline withquestions answer --declined, and cancel withquestions cancel.
