Skip to main content

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/questions
  • GET /v1/sessions/{session_id}/questions
Resume from a session context:
  • POST /v1/sessions/{session_id}/approvals
  • POST /v1/sessions/{session_id}/questions
  • POST /v1/sessions/{session_id}/approval-runs
Resume from a run handle:
  • POST /v1/runs/{run_id}/approvals
  • POST /v1/runs/{run_id}/questions

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 suspended run_id and want a detached resume handle. Behavior difference:
  • POST /v1/sessions/{session_id}/approvals
    • returns an updated SessionView
  • POST /v1/sessions/{session_id}/questions
    • returns an updated SessionView
  • POST /v1/sessions/{session_id}/approval-runs
    • returns 202 Accepted plus a detached RunView
  • POST /v1/runs/{run_id}/approvals
    • returns 202 Accepted plus a detached RunView
  • POST /v1/runs/{run_id}/questions
    • returns 202 Accepted plus a detached RunView

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_id
  • agent_id
  • run_id
  • request
request is a UserQuestionRequest:
  • id
  • tool_call_id
  • questions
  • created_at_ms
Each structured question contains:
  • id
  • header
  • question
  • options
  • multi_select

Resolve approvals

Approval endpoints accept:
{
  "resolutions": [
    {
      "request_id": "approval-bash-1",
      "behavior": "allow",
      "justification": "approved by operator"
    }
  ]
}
Each ApprovalResolution supports:
  • request_id
  • behavior
    • allow
    • deny
  • updated_input
  • justification
  • reason
Use updated_input when the approver wants to modify the pending tool input before allowing it.

Resolve structured user questions

Question endpoints accept:
{
  "resolution": {
    "request_id": "question-1",
    "answers": [
      {
        "question_id": "routing",
        "selected_option_ids": ["openai"]
      },
      {
        "question_id": "notes",
        "freeform_answer": "Use the fast path unless cost exceeds budget."
      }
    ],
    "declined": false,
    "justification": "Answered by operator"
  }
}
UserQuestionResolution fields:
  • request_id
  • answers
  • declined
  • justification
Each answer supports:
  • question_id
  • selected_option_ids
  • freeform_answer

Run states

These endpoints are relevant when a run enters one of these states:
  • waiting_for_approval
  • waiting_for_user_question
You can inspect those states through Sessions and runs API using:
  • GET /v1/sessions/{session_id}
  • GET /v1/runs/{run_id}
Relevant fields on RunView:
  • pending_approval_ids
  • pending_question_ids
  • pending_questions

Operational notes

  • Approval resolution is batch-based: send every decision you want to resolve in the current request.
  • 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.