> ## Documentation Index
> Fetch the complete documentation index at: https://docs.pinework.ai/llms.txt
> Use this file to discover all available pages before exploring further.

# Question

> A question is an agent's ask to a person, with four answer modes and two states. Agents can never answer one.

A question is an ask that an agent raises inside a run, which a person answers with text, option keys or a secret. Its id starts with `qst_`. For why agents ask and how the run waits, see [Approvals and questions](/concepts/approvals-and-questions).

## States

| State | Meaning | Ends here |
| - | - | - |
| `open` | The question waits for an answer. | No |
| `resolved` | A person answered it, or the system closed it. | Yes |

| From | To | Trigger |
| - | - | - |
| (none) | `open` | An agent runs `pinework question ask`, or calls `POST /api/v1/questions` inside a run. |
| `open` | `resolved` | A person answers with `pinework question answer`, the answer route, or the secret route. |
| `open` | `resolved` | A person replies in the thread of the question's message on a task or conversation. |
| `open` | `resolved` | The system closes it: the run ends without pausing, the run fails to stop, or an in-run wait times out. |

A system close sets `answer.text` to `[system:<reason>]` and leaves `answeredBy` null. For a `secret_input` question, `answer` stays null.

## Answer modes

| `answerMode` | `options` | How you answer |
| - | - | - |
| `free_text` | Not allowed | `answer.text` must be non-empty. `answer.selected` must be null or empty. |
| `single_select` | Required | `answer.selected` holds exactly one option key. |
| `multi_select` | Required | `answer.selected` holds one or more option keys. `answer.text` is an optional note. |
| `secret_input` | Not allowed | Send the value to the secret route. It becomes a Keychain secret granted to the asking agent. |

A thread reply stores your reply as `answer.text` with `selected` null. It works for every mode except `secret_input`.

## Fields

<ResponseField name="id" type="string" required>Question id, `qst_…`.</ResponseField>
<ResponseField name="object" type="string" required>Always `question`.</ResponseField>
<ResponseField name="workspaceId" type="string" required>Workspace the question belongs to.</ResponseField>
<ResponseField name="taskId" type="string">Linked task, `tsk_…`. Null when the ask named no task.</ResponseField>
<ResponseField name="conversationId" type="string">Conversation the answer shows in, `cnv_…`. Null when there is none.</ResponseField>
<ResponseField name="runId" type="string" required>Run that asked, `run_…`.</ResponseField>
<ResponseField name="sessionId" type="string" required>Session that asked.</ResponseField>
<ResponseField name="status" type="string" required>`open` or `resolved`.</ResponseField>
<ResponseField name="prompt" type="string" required>The question text, in Markdown, with secrets redacted.</ResponseField>
<ResponseField name="answerMode" type="string" required>`free_text`, `single_select`, `multi_select` or `secret_input`.</ResponseField>

<ResponseField name="options" type="object[]">
  Choices for select modes. Null for `free_text` and `secret_input`.

  <Expandable title="option">
    <ResponseField name="key" type="string" required>Stable key, unique within the question.</ResponseField>
    <ResponseField name="label" type="string" required>Display text.</ResponseField>
  </Expandable>
</ResponseField>

<ResponseField name="secretRequest" type="object">
  What a `secret_input` question asks for. Null for other modes.

  <Expandable title="secretRequest">
    <ResponseField name="name" type="string" required>Keychain name for the new secret. It must be free in the workspace.</ResponseField>
    <ResponseField name="provider" type="string" required>Provider the secret is for, such as `stripe` or `github`.</ResponseField>
    <ResponseField name="envVarName" type="string" required>Environment variable the agent receives it under.</ResponseField>
    <ResponseField name="reason" type="string" required>Why the agent needs it.</ResponseField>
  </Expandable>
</ResponseField>

<ResponseField name="askedBy" type="object" required>Who asked. `type` is `agent` with an `agt_…` id, or `system`.</ResponseField>

<ResponseField name="answer" type="object">
  The answer. Null while `open`.

  <Expandable title="answer">
    <ResponseField name="text" type="string">Free text or a note. Null when not given.</ResponseField>
    <ResponseField name="selected" type="string[]">Chosen option keys. Null for `free_text`.</ResponseField>
    <ResponseField name="secretId" type="string">Keychain secret the answer created, `sec_…`. Present for `secret_input` only.</ResponseField>
  </Expandable>
</ResponseField>

<ResponseField name="answeredBy" type="object">The answering person, `{ "type": "user", "id": "usr_…" }`. Null while open or after a system close.</ResponseField>
<ResponseField name="answeredAt" type="string">Answer time. Null while `open`.</ResponseField>
<ResponseField name="createdAt" type="string" required>Creation time.</ResponseField>
<ResponseField name="updatedAt" type="string" required>Last change time.</ResponseField>

`GET /api/v1/questions/{id}` also accepts `include[]` with `task`, `run`, `session` or `conversation` to embed those objects.

## CLI

| Command | Does |
| - | - |
| `question ask` | Asks a free-text question from inside an agent run, then the run stops. With `--secret-name`, `--provider` and `--env`, it asks for a secret instead. |
| `question answer` | Answers one question as you. Pass text, or `--select` once per option key. An agent run is refused. |
| `question get` | Shows one question and its answer. |
| `question list` | Lists questions. Filter by `--status`, `--task`, `--run`, `--session`, `--conversation` or `--asked-by-agent-id`. Page with `--starting-after`. |

The CLI asks only `free_text` and `secret_input` questions.

```bash theme={null}
pinework question ask PIN-42 "Ship the drop in this PR or the next one?"
pinework question list --status open --task PIN-42
pinework question answer qst_AbCdEfGh12345678 "Ship it in the next one"
pinework question answer qst_AbCdEfGh12345678 --select a --select c
```

## API

| Method | Path | Does |
| - | - | - |
| `POST` | `/api/v1/questions` | Asks a question from an agent run. Needs an `Idempotency-Key` header. Returns `{ question, sentinel }` with status 201. |
| `GET` | `/api/v1/questions` | Lists questions, newest first. Filters: `status`, `taskId`, `runId`, `sessionId`, `conversationId`, `askedByAgentId`. Cursors: `startingAfter`, `endingBefore`. |
| `GET` | `/api/v1/questions/{id}` | Gets one question. Accepts `include[]`. |
| `POST` | `/api/v1/questions/{id}/answer` | Answers a non-secret question with `{ "answer": { "text", "selected" } }`. Needs an `Idempotency-Key` header. |
| `POST` | `/api/v1/questions/{id}/secret` | Answers a `secret_input` question with `{ "value" }`. The value is never returned. |

## Errors

| Code | HTTP | When | What to do |
| - | - | - | - |
| `user_cannot_create_question` | 403 | A user credential calls `POST /questions`. | Ask from inside an agent run. |
| `question_run_required` | 404 | The ask has no run, or its run is not found. | Ask from inside an agent run. |
| `options_required` | 400 | A select question has no options. | Send at least one option. |
| `options_not_allowed` | 400 | A `free_text` or `secret_input` question has options. | Drop `options`. |
| `secret_request_required` | 400 | A `secret_input` question has no `secretRequest`. | Send `secretRequest`. |
| `secret_request_not_allowed` | 400 | A non-secret question has a `secretRequest`. | Drop `secretRequest`. |
| `secret_input_requires_agent` | 400, 409 | No agent asked, or the asking agent no longer exists. | Ask as an agent. |
| `secret.duplicate_name` | 409 | A secret with that name already exists. | Pick another `--secret-name`. |
| `question_task_scope_mismatch` | 404 | The linked task is not found or not visible. | Check the task id. |
| `hitl_stop_failed` | 409 | The run could not be stopped after asking. | Retry the ask. |
| `question_not_a_gate` | 400 | The body carries `outcome`, `scope` or `appliedScope`. | Use an approval for a yes or no gate. |
| `agent_cannot_answer_question` | 403 | An agent tries to answer. | A person must answer. |
| `question_not_found` | 404 | No such question in the workspace. | Check the `qst_` id. |
| `question_resolved` | 409 | The question is already answered. | Read it with `question get`. |
| `question_needs_secret` | 409 | You sent a plain answer to a `secret_input` question. | Use the secret route. |
| `question_not_secret_input` | 409 | You sent a secret to a question of another mode. | Use the answer route. |
| `secret_provider_unbrokerable` | 409 | The requested provider can no longer be brokered. | Ask again with a supported provider. |
| `invalid_answer` | 400 | A `free_text` answer has empty text. | Send non-empty text. |
| `answer_mode_mismatch` | 400 | The answer shape does not fit the mode. | Match the shape in Answer modes. |
| `invalid_option_key` | 400 | A selected key is not one of the options. | Use a key from `options`. |
| `invalid_include` | 400 | An `include[]` value is unknown. | Use `task`, `run`, `session` or `conversation`. |

An agent that calls the secret route gets 403 `http_exception`. For shared codes, see [API errors](/reference/api#errors).

## Limits

* A list returns 25 questions by default and 100 at most.
* `secretRequest.reason` holds 1 to 2,000 characters.
* A secret value holds 1 to 10,000 characters.

<CardGroup cols={2}>
  <Card title="Approvals and questions" href="/concepts/approvals-and-questions">
    Why agents ask, and how the run waits.
  </Card>

  <Card title="Answer approvals" href="/guides/answer-approvals">
    Answer from the inbox, the task or the terminal.
  </Card>

  <Card title="Approval" href="/reference/approval">
    The yes or no gate, for comparison.
  </Card>

  <Card title="Secret" href="/reference/secret">
    What a `secret_input` answer creates.
  </Card>
</CardGroup>


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.