Asks · decideAsk
Decide an ask (humans only)
POST /v1/asks/{id}/decision
A signed-in Decider or Owner decides: an option, a text (when the ask allows it), or both. The first decision wins. Agents can never decide asks.
- Who may call it
- humans: Deciders and up
- Server
- https://api.logu.dev
Parameters#
| Name | In | Type | Required | Description |
|---|---|---|---|---|
id | path | string | required | The ask's id (ask_…). · e.g. ask_01K7DQ3M2V8H4N6R9T1W3Y5B7C |
Logu-Workspace | header | string | optional | Humans only: the workspace to act in (default: your personal workspace). Agents always act in their API key's workspace. · e.g. wsp_01K7D7Y8Z0B2D4F6H8K0M2P4R6 |
Request body#
Content type application/json (required).
A human's decision: an option, a text, or both.
| Field | Type | Required | Notes |
|---|---|---|---|
optionId | string | optional | 1–64 chars · pattern ^[a-z0-9][a-z0-9._-]*$ |
text | string | optional | 1–5000 chars |
{
"optionId": "approve"
}
Example#
curl -s -X POST "https://api.logu.dev/v1/asks/ask_01K7DQ3M2V8H4N6R9T1W3Y5B7C/decision" \
-H "Authorization: Bearer $LOGU_SESSION_TOKEN" \
-H "Content-Type: application/json" \
-d '{"optionId":"approve"}'
Responses#
200
The ask, decided, with decision.decidedBy.
{
"id": "ask_01K7DQ3M2V8H4N6R9T1W3Y5B7C",
"project": "shop",
"channel": null,
"title": "Deploy v1.4 to production?",
"body": "3 commits since v1.3. CI is green.",
"icon": null,
"url": "https://github.com/acme/shop/compare/v1.3...v1.4",
"options": [
{
"id": "approve",
"label": "Approve",
"style": "primary"
},
{
"id": "decline",
"label": "Decline",
"style": "danger"
}
],
"allowText": false,
"status": "decided",
"decision": {
"optionId": "approve",
"text": null,
"decidedVia": "app",
"decidedAt": "2026-10-12T14:05:42.000Z",
"decidedBy": {
"id": "Xq3bY7tLmN2pR8sV",
"name": "Ana"
}
},
"defaultOption": "decline",
"expiresAt": "2026-10-12T15:03:11.000Z",
"createdAt": "2026-10-12T14:03:11.000Z",
"metadata": null,
"agent": {
"id": "agt_01K7D8F2H4K6M8P0R2T4V6X8Z0",
"name": "deploy-bot"
},
"attachments": [
{
"id": "att_01K7DQ6C8E0G2J4L6N8Q0S2U4W",
"url": "https://api.logu.dev/a/att_01K7DQ6C8E0G2J4L6N8Q0S2U4W?exp=1791824400&sig=q8Hk2mP0vX3nR5tY7wZ9bD1fG3jL5nQ7sU9wY1aC3eE",
"contentType": "image/png",
"size": 184320,
"width": 1440,
"height": 900,
"name": "checkout-after.png"
}
],
"test": false
}
Response fields
| Field | Type | Required | Notes |
|---|---|---|---|
id | string | required | |
project | string | required | |
channel | string | null | required | |
title | string | required | |
body | string | null | required | |
icon | string | null | required | |
url | string | null | required | |
options | AskOption[] | required | |
options[].id | string | required | 1–64 chars · pattern ^[a-z0-9][a-z0-9._-]*$ |
options[].label | string | required | 1–64 chars |
options[].style | "primary" | "danger" | "default" | required | |
allowText | boolean | required | |
status | "waiting" | "decided" | "timed_out" | "withdrawn" | required | |
decision | Decision | null | required | |
decision.optionId | string | null | required | |
decision.text | string | null | required | |
decision.decidedVia | "app" | "link" | "telegram" | "slack" | "whatsapp" | "rule" | "timeout" | required | |
decision.decidedAt | string | required | date-time |
decision.decidedBy | MemberRef | null | required | |
decision.decidedBy.id | string | required | |
decision.decidedBy.name | string | required | |
decision.ruleId | string | null | optional | |
defaultOption | string | null | optional | Applied as the decision if nobody decides before expiresAt (then decision.decidedVia is timeout: never an approval); null: it ends timed_out. Agents can't set an approving one (id approve or style primary) on new asks. |
expiresAt | string | required | date-time |
createdAt | string | required | date-time |
metadata | object | null | required | Free-form context stored with the item, up to 8,192 characters of JSON. thread (a short string, such as a Claude Code session id or a Codex thread id) is the one key Logu reads: events that share it are shown together as one thread in the app. |
agent | AgentRef | null | required | |
agent.id | string | required | |
agent.name | string | required | |
attachments | Attachment[] | required | Its images, in order, each with a signed url that works for about an hour. |
attachments[].id | string | required | |
attachments[].url | string | required | A signed link to the image that works without credentials for about an hour (re-read the ask or event for a fresh one). Members can always fetch it with GET /v1/attachments/{id}. |
attachments[].contentType | "image/png" | "image/jpeg" | "image/webp" | "image/gif" | required | |
attachments[].size | integer | required | Bytes. |
attachments[].width | integer | null | required | Pixels, when Logu could read them from the image's header. |
attachments[].height | integer | null | required | Pixels, when Logu could read them from the image's header. |
attachments[].name | string | null | required | The file name it was uploaded with, if any (the image's alt text in the app). |
test | boolean | required | Sent with a test API key (lg_test_…): shown in the app and decided there like any other, but never delivered (no push, browser notification, email or Telegram). Its callback is sent. |
escalation | object | null | optional | |
escalation.at | string | required | date-time |
escalation.to | MemberRef[] | required | |
escalation.to[].id | string | required | |
escalation.to[].name | string | required |
Errors#
Every error is {"error":{"code","message","hint","docUrl","requestId"}}: branch on code. See Errors.
| Status | Code | Meaning |
|---|---|---|
| 400 | bad_request | The request is malformed or a field is invalid (see issues). |
| 400 | unknown_field | The body has a field the endpoint doesn't know (often a typo or snake_case). |
| 401 | unauthorized | No credentials, or the API key or session is invalid or revoked. |
| 403 | forbidden | The caller is authenticated but may not do this (wrong role, or an API key on a human-only endpoint). |
| 404 | not_found | No such route, or the resource doesn't exist in your workspace. |
| 409 | conflict | The resource isn't in a state that allows this: an ask that is no longer waiting (ask has its current state), an invitation no longer pending, the last Owner. |
| 500 | internal_error | Something went wrong on Logu's side. |