Answer a human approval
POST/api/v1/asks/{id}/answer
scope
asks:answerIdempotentCosts 2 rate-limit unitsAnswers the question and un-parks the run. If somebody answered first this returns 200 with already_answered: true, NOT a conflict — what you wanted did happen, just not by your call, and a 409 would invite a retry that cannot succeed. min_role on an ask governs the dashboard; over the API the asks:answer scope is the gate, and the answer is attributed to the key rather than to a person.
Path parameters
| Name | Type | Description |
|---|---|---|
idrequired | string (uuid) | Ask id |
Body
application/json| Name | Type | Description |
|---|---|---|
decisionrequired | string (enum) | approve or reject an approval, choose an option, submit input, or edit a tool callapproverejectchoosesubmitedit |
answeroptional | map<string, any> | The payload for the decision — `option_id` for a choice, `text` for input |
Request
curl -X POST "https://your-crm.example.com/api/v1/asks/6d7e8f9a-0b1c-4d2e-8f3a-4b5c6d7e8f9a/answer" \
-H "Authorization: Bearer $RAABTA_API_KEY" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: 6f1c2a9e-3b4d-4c5e-8f7a-9b0c1d2e3f4a" \
-d '{
"decision": "approve"
}'Response · 200 OK
{
"data": {
"ask": {
"id": "6d7e8f9a-0b1c-4d2e-8f3a-4b5c6d7e8f9a",
"run_id": "3f9a1c2e-5b7d-4e8f-9a0b-1c2d3e4f5a6b",
"assistant_id": "8f2c1d3e-4a5b-4c6d-9e0f-1a2b3c4d5e6f",
"kind": "approval",
"status": "answered",
"question": "Offer Ayesha a free reschedule outside the 24-hour window?",
"context": "She cancelled 3 hours before her slot and has been a customer for two years.",
"options": [],
"tool_id": null,
"tool_input": null,
"min_role": "admin",
"expires_at": "2026-09-03T09:00:00.000Z",
"on_timeout": "reject",
"decision": "approve",
"answer": null,
"answered_by": null,
"answered_at": null,
"answer_source": "human",
"created_at": "2026-09-02T09:01:40.000Z",
"updated_at": "2026-09-02T09:01:40.000Z"
},
"resumed": true,
"already_answered": false
}
}Response
Wrapped in { data: … }| Name | Type | Description |
|---|---|---|
askrequired | object | The ask as it now stands |
ask.idrequired | string (uuid) | Ask id |
ask.run_idrequired | string (uuid) | The run parked on this question |
ask.assistant_idrequired | string (uuid) | null | The assistant that asked |
ask.kindrequired | string (enum) | What kind of answer the run needsapprovalchoiceinputtool_call |
ask.statusrequired | string (enum) | Only a pending ask can be answeredpendingansweredexpiredcancelled |
ask.questionrequired | string | What the assistant is asking |
ask.contextrequired | string | null | Background it supplied with the question |
ask.optionsrequired | object[] | For a choice: the options offered. Empty for every other kind |
ask.options[].idrequired | string | Pass this back as option_id |
ask.options[].labelrequired | string | What to show a human |
ask.options[].descriptionrequired | string | null | Extra detail, if the assistant gave any |
ask.tool_idrequired | string | null | For a tool_call: the tool the model proposed and has NOT run |
ask.tool_inputrequired | map<string, any> | null | The arguments it proposed |
ask.min_rolerequired | string | The dashboard role floor for answering. NOT enforced for API keys, where the asks:answer scope is the gate |
ask.expires_atrequired | string (date-time) | After this the on_timeout rule applies automatically |
ask.on_timeoutrequired | string (enum) | What happens if nobody answers in timerejectapprovehandofffail |
ask.decisionrequired | string | null | The decision recorded, once answered |
ask.answerrequired | map<string, any> | null | The answer recorded |
ask.answered_byrequired | string (uuid) | null | The member who answered. Null when an API key, a timeout or a withdrawal settled it |
ask.answered_atrequired | string (date-time) | null | When it was answered |
ask.answer_sourcerequired | string (enum) | null | How it settledhumantimeoutcancel |
ask.created_atrequired | string (date-time) | When the assistant asked |
ask.updated_atrequired | string (date-time) | When it last changed |
resumedrequired | boolean | Whether this answer un-parked the run. False when the run had already ended |
already_answeredrequired | boolean | True when somebody answered first. Your answer was not recorded, and this is not an error |
Errors
| Status | Code | When |
|---|---|---|
| 400 | bad_request | The request could not be parsed: malformed JSON, an invalid cursor, or a query parameter of the wrong shape. |
| 400 | validation_error | The body or query failed validation. `details` lists each failing field with a `path` and a `message`. |
| 401 | unauthorized | No usable API key: the Authorization header is missing or malformed, or the key is unknown, revoked or expired. The three are deliberately indistinguishable. |
| 403 | forbidden | The key is valid but lacks the scope this endpoint requires, or the request came from an address outside the key’s IP allowlist. The message says which. |
| 403 | account_suspended | The account this key belongs to is suspended. Rotating the key will not help; contact support. |
| 404 | not_found | No such resource in this account. A resource that exists in another account also returns this. |
| 409 | conflict | The request conflicts with current state: a duplicate phone number on create, an invalid state transition, or an idempotent request that is still in flight. |
| 422 | unprocessable | The request was well-formed but cannot be carried out. `reason` is a stable string saying why (for example `outside_window` or `stage_not_in_pipeline`). |
| 429 | rate_limited | The per-key budget, or the per-IP budget for failed authentication, is exhausted. Honour `Retry-After` before retrying. |
| 500 | internal | Something failed on our side. Safe to retry with the same Idempotency-Key; quote `request_id` if it persists. |