# Responses

> The Responses operations of the Kabu API: how a human (or a delegate) settles an ask.

How a human (or a delegate) settles an ask.

Generated from the contract, version `2.0.0-draft.2` ([OpenAPI](/openapi.yaml)). Base URL `https://usekabu.com/api`. Errors are problem details, each [code](/docs/errors) with its own page.

## `POST /v2/messages/{message_id}/responses` · A human responds to an ask

Called by the Kabu apps, by the answer page behind a delivered link, or by an integrator key with answers:delegate (recorded as delegated, with asserted_by). Any key holding messages:write gets 403 self_response_forbidden (test mode excepted), so an agent can never answer its own or a sibling's ask. After settlement: 409 already_settled with the settled message; after the deadline: 410 ask_expired.

- **Auth:** an API key, or a signed-in human's token (the Kabu apps).
- **Scopes:** `answers:delegate`.
- **Refused to:** a key holding `messages:write`.
- **Signed-in humans:** `audience`.

| Parameter | In | Type | Required | Description |
|---|---|---|---|---|
| `message_id` | path | string | yes | A `msg_…` id. |
| `Idempotency-Key` | header | string, at most 255 characters | no | Retry-safe key. For messages, sessions and responses it binds to the created object for its life; elsewhere for 24 h. |
| `Kabu-Org` | header | string | no | Select a live membership (default personal org); non-member is 404. Human tokens are live-only. Caller user id is distinct from billing owner. |

**Request body**: [ResponseInput](#schema-ResponseInput).

| Status | Means | Body |
|---|---|---|
| `200` | Settled; the message with its resolution. | [Message](/docs/api/messages#schema-Message) |
| `4XX` | Error | [Problem](/docs/api/discovery#schema-Problem) |
| `5XX` | Error | [Problem](/docs/api/discovery#schema-Problem) |

```sh
curl -X POST "https://usekabu.com/api/v2/messages/$MESSAGE_ID/responses" \
  -H "Authorization: Bearer $KABU_API_KEY" \
  -H "Idempotency-Key: $(uuidgen)" \
  -H "Content-Type: application/json" \
  -d '{"decision":"approve"}'
```

## `POST /v2/messages/{message_id}/answer_links` · Mint a single-use answer link for one recipient

For integrators who deliver asks through their own channels. Audited. A key holding messages:write gets 403 self_response_forbidden. Kabu-delivered links never appear in API bodies.

- **Auth:** an API key.
- **Scopes:** `answers:delegate`.
- **Refused to:** a key holding `messages:write`.

| Parameter | In | Type | Required | Description |
|---|---|---|---|---|
| `message_id` | path | string | yes | A `msg_…` id. |
| `Idempotency-Key` | header | string, at most 255 characters | no | Retry-safe key. For messages, sessions and responses it binds to the created object for its life; elsewhere for 24 h. |

**Request body**: [AnswerLinkCreate](#schema-AnswerLinkCreate).

| Status | Means | Body |
|---|---|---|
| `201` | OK | [AnswerLink](#schema-AnswerLink) |
| `4XX` | Error | [Problem](/docs/api/discovery#schema-Problem) |
| `5XX` | Error | [Problem](/docs/api/discovery#schema-Problem) |

```sh
curl -X POST "https://usekabu.com/api/v2/messages/$MESSAGE_ID/answer_links" \
  -H "Authorization: Bearer $KABU_API_KEY" \
  -H "Idempotency-Key: $(uuidgen)" \
  -H "Content-Type: application/json" \
  -d '{"recipient":"…"}'
```

## `POST /v2/receipts/verify` · Verify a receipt and return its claims

For integrators without a JWS library. Checks signature, issuer and that the receipt belongs to the caller's org and mode; an invalid receipt is 422 invalid_receipt. Offline verification against /.well-known/jwks.json gives the same answer.

- **Auth:** an API key, or a signed-in human's token (the Kabu apps).
- **Scopes:** `messages:read`.
- **Signed-in humans:** `audience`.

| Parameter | In | Type | Required | Description |
|---|---|---|---|---|
| `Kabu-Org` | header | string | no | Select a live membership (default personal org); non-member is 404. Human tokens are live-only. Caller user id is distinct from billing owner. |

**Request body**: [ReceiptVerify](#schema-ReceiptVerify).

| Status | Means | Body |
|---|---|---|
| `200` | OK | [ReceiptClaims](#schema-ReceiptClaims) |
| `4XX` | Error | [Problem](/docs/api/discovery#schema-Problem) |
| `5XX` | Error | [Problem](/docs/api/discovery#schema-Problem) |

```sh
curl -X POST "https://usekabu.com/api/v2/receipts/verify" \
  -H "Authorization: Bearer $KABU_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"receipt":"…"}'
```

## Schemas

### ResponseInput

Validated against the stored ask. `as_recipient` is required for delegated responses and ignored otherwise.

Type: [ApproveInput](#schema-ApproveInput) or [RejectInput](#schema-RejectInput) or [IgnoreInput](#schema-IgnoreInput) or [EditInput](#schema-EditInput) or [RespondInput](#schema-RespondInput).

Chosen by `decision`: `"approve"`: [ApproveInput](#schema-ApproveInput), `"reject"`: [RejectInput](#schema-RejectInput), `"ignore"`: [IgnoreInput](#schema-IgnoreInput), `"edit"`: [EditInput](#schema-EditInput), `"respond"`: [RespondInput](#schema-RespondInput).

### ApproveInput

`option` is required when the ask offers more than one approve option (e.g. once vs session).

| Property | Type | Required | Description |
|---|---|---|---|
| `decision` | `"approve"` | yes |  |
| `option` | string | no |  |
| `comment` | string, at most 1000 characters | no |  |
| `as_recipient` | [RecipientRef](/docs/api/messages#schema-RecipientRef) | no | `rcp_…`, `ext:<your external id>`, or `me` (the key's `default_to`). |
| `surface` | [Surface](#schema-Surface) | no | Records via for a user token only; never raises assurance. Links remain link, delegates delegated. Web BFF forces web. |

Unknown properties are refused.

### Surface

Records via for a user token only; never raises assurance. Links remain link, delegates delegated. Web BFF forces web.

Type: string, one of `"app"`, `"lock_screen"`, `"watch"`, `"live_activity"`, `"web"`.

### RejectInput

| Property | Type | Required | Description |
|---|---|---|---|
| `decision` | `"reject"` | yes |  |
| `option` | string | no |  |
| `comment` | string, at most 1000 characters | no |  |
| `as_recipient` | [RecipientRef](/docs/api/messages#schema-RecipientRef) | no | `rcp_…`, `ext:<your external id>`, or `me` (the key's `default_to`). |
| `surface` | [Surface](#schema-Surface) | no | Records via for a user token only; never raises assurance. Links remain link, delegates delegated. Web BFF forces web. |

Unknown properties are refused.

### IgnoreInput

| Property | Type | Required | Description |
|---|---|---|---|
| `decision` | `"ignore"` | yes |  |
| `option` | string | yes |  |
| `comment` | string, at most 1000 characters | no |  |
| `as_recipient` | [RecipientRef](/docs/api/messages#schema-RecipientRef) | no | `rcp_…`, `ext:<your external id>`, or `me` (the key's `default_to`). |
| `surface` | [Surface](#schema-Surface) | no | Records via for a user token only; never raises assurance. Links remain link, delegates delegated. Web BFF forces web. |

Unknown properties are refused.

### EditInput

Only when the ask has `action` and `allow_edit`. A full replacement of action.args, validated against args_schema.

| Property | Type | Required | Description |
|---|---|---|---|
| `decision` | `"edit"` | yes |  |
| `edited_args` | object, at least 1 keys | yes |  |
| `comment` | string, at most 1000 characters | no |  |
| `as_recipient` | [RecipientRef](/docs/api/messages#schema-RecipientRef) | no | `rcp_…`, `ext:<your external id>`, or `me` (the key's `default_to`). |
| `surface` | [Surface](#schema-Surface) | no | Records via for a user token only; never raises assurance. Links remain link, delegates delegated. Web BFF forces web. |

Unknown properties are refused.

### RespondInput

Exactly one of: option (single choice), options (multiple choice), text (text ask, or choice with allow_other), values (form). Anything else is 400.

| Property | Type | Required | Description |
|---|---|---|---|
| `decision` | `"respond"` | yes |  |
| `option` | string | no |  |
| `options` | array of string, 1 to 12 items | no |  |
| `text` | string, at most 4000 characters | no |  |
| `values` | [FormValues](/docs/api/messages#schema-FormValues) | no |  |
| `confidence` | integer, from 1 to 5 | no |  |
| `comment` | string, at most 1000 characters | no |  |
| `as_recipient` | [RecipientRef](/docs/api/messages#schema-RecipientRef) | no | `rcp_…`, `ext:<your external id>`, or `me` (the key's `default_to`). |
| `surface` | [Surface](#schema-Surface) | no | Records via for a user token only; never raises assurance. Links remain link, delegates delegated. Web BFF forces web. |

Unknown properties are refused.

### AnswerLinkCreate

| Property | Type | Required | Description |
|---|---|---|---|
| `recipient` | [RecipientRef](/docs/api/messages#schema-RecipientRef) | yes | `rcp_…`, `ext:<your external id>`, or `me` (the key's `default_to`). |
| `expires_in` | integer, from 60 to 86400 | no |  |

Unknown properties are refused.

### AnswerLink

A single-use capability. Deliver it only to that recipient.

| Property | Type | Required | Description |
|---|---|---|---|
| `object` | `"answer_link"` | yes |  |
| `url` | string (uri) | yes |  |
| `recipient` | string | yes |  |
| `single_use` | boolean | yes |  |
| `expires_at` | [Timestamp](/docs/api/messages#schema-Timestamp) | yes |  |

### ReceiptVerify

| Property | Type | Required | Description |
|---|---|---|---|
| `receipt` | string, at most 8192 characters | yes |  |

Unknown properties are refused.

### ReceiptClaims

The signed payload of Resolution.receipt. Kabu attests what it recorded: the proposal it delivered and the response it received, not pixels seen or downstream execution. Keys rotate yearly and remain in the JWKS for the audit period; alg must be ES256.

| Property | Type | Required | Description |
|---|---|---|---|
| `v` | `"1"` | yes |  |
| `iss` | string | yes | `https://api.usekabu.com` |
| `aud` | string | yes | The org id. Verifiers must check it. |
| `livemode` | boolean | yes |  |
| `jti` | string | yes | Unique receipt id. |
| `iat` | integer | yes |  |
| `msg` | string | yes | The message id. |
| `rev` | integer | yes | Request revision; always 1 (asks are immutable). |
| `req_digest` | string | yes | sha256 of RFC 8785 JSON of {org, livemode, msg, rev, title, body, blocks, links, attachments:[{media, sha256}], action, ask} with quiz.correct and all delivery state, URLs and receipts excluded. |
| `decision` | string | yes |  |
| `value_digest` | string or null | yes |  |
| `effective_action_digest` | string or null | yes |  |
| `grant` | [Grant](/docs/api/messages#schema-Grant) or null | yes |  |
| `sub` | string | yes | The responding human (rcp_…) or `system`. |
| `asserted_by` | string or null | yes |  |
| `assurance` | string | yes |  |
| `source` | string | yes |  |
| `via` | string | yes |  |
| `resolved_at` | integer | yes |  |
