{"openapi":"3.1.0","info":{"title":"Ask-the-App API","version":"0.2.0","summary":"Ask for changes to a project board in plain words; review them; apply or undo.","description":"The HTTP interface behind the Ask-the-App demo. The assistant proposes; people apply. `POST /api/ask` never writes to a task. Only `apply` and `undo` do, each as one atomic step that re-checks every task first. Every JSON endpoint reports failure in the same envelope (`Error`) and sends an `X-Request-Id` header. There is no sign-in: the demo workspace is shared and is reseeded nightly."},"servers":[{"url":"/","description":"This deployment"}],"tags":[{"name":"Assistant","description":"Ask in plain words; get a proposal, a question or a refusal. Never a write."},{"name":"Changes","description":"The proposals the assistant made, and the only calls that write to tasks."},{"name":"Workspace","description":"Read the board, the team and the sprints."},{"name":"Platform","description":"Health, the nightly reseed and this document."}],"x-error-codes":[{"code":"invalid_request","statuses":[400],"meaning":"The request is not valid: bad JSON, too large, a field out of range, or a malformed id."},{"code":"forbidden_origin","statuses":[403],"meaning":"A browser sent a write from another site. Calls without an Origin header are accepted."},{"code":"unauthorized","statuses":[401],"meaning":"The cron route needs its bearer secret."},{"code":"not_found","statuses":[404],"meaning":"No such change set, or no such endpoint."},{"code":"not_proposed","statuses":[409],"meaning":"The change set is no longer a proposal, so it cannot be applied or cancelled. `status` says what it is."},{"code":"not_applied","statuses":[409],"meaning":"The change set is not in the applied state, so it cannot be undone. `status` says what it is."},{"code":"stale","statuses":[409],"meaning":"A task changed since the proposal (or the apply). Nothing was written; `conflicts` lists the rows."},{"code":"rate_limited","statuses":[429],"meaning":"Too many requests from this address. Wait `retryAfterSeconds`."},{"code":"budget_exhausted","statuses":[503],"meaning":"The assistant spent its daily model budget. `resetsAt` is the next 00:00 UTC."},{"code":"unavailable","statuses":[503],"meaning":"No model is configured on this deployment."},{"code":"not_configured","statuses":[503],"meaning":"The route is closed because the deployment has no secret for it."},{"code":"internal","statuses":[500],"meaning":"Something failed on the server. Nothing was changed; quote `requestId` in a report."}],"paths":{"/api/ask":{"post":{"operationId":"ask","tags":["Assistant"],"summary":"Ask the assistant for a change","description":"Turns a request typed in plain words into a proposal, a question back, a refusal or a short answer. The assistant never changes a task: a proposal is stored as a change set in the `proposed` state and only `POST /api/changes/{id}/apply` writes to tasks. The response is a stream of newline-delimited JSON: any number of `activity` events while tools run, then exactly one `result` or one `error`. Failures before the model starts (origin, rate limit, validation, budget) are ordinary JSON errors with a status code, not a stream. Destructive requests (\"delete all tasks\") are refused before any model runs, at no cost. Asks are limited to 20 per ten minutes per address, and the assistant has a hard daily token budget. A browser request must come from this site (the Origin header is checked); a request with no Origin, such as curl, is accepted.","requestBody":{"required":true,"description":"The request, and optionally the option the person picked after a question (`followUp`).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/AskRequest"},"example":{"prompt":"Move all of Priya's tasks due this week to next sprint"}}}},"responses":{"200":{"description":"A stream of events, one JSON object per line (`application/x-ndjson`). Each line matches the AskEvent schema.","headers":{"X-Request-Id":{"description":"Identifies the request in the server logs. Quote it when reporting a problem.","schema":{"type":"string"}},"Server-Timing":{"description":"Milliseconds the server spent before it began to answer, as `app;dur=<ms>`. For a streamed answer, before the first byte.","schema":{"type":"string","pattern":"^app;dur=\\d+$"}}},"content":{"application/x-ndjson":{"schema":{"$ref":"#/components/schemas/AskEvent"},"example":"{\"type\":\"activity\",\"tool\":\"search_tasks\",\"label\":\"Searching tasks\"}\n{\"type\":\"activity\",\"tool\":\"propose_changes\",\"label\":\"Checking changes against the policy\"}\n{\"type\":\"result\",\"askId\":\"9d1c7a4e-2c61-4f52-8c0b-5b1d0a6f7e21\",\"result\":{\"kind\":\"proposal\",\"changeSet\":{\"id\":\"6c1a0c1e-5f0e-4d9e-9a57-2f1d3c6d9b10\",\"prompt\":\"Move all of Priya's tasks due this week to next sprint\",\"summary\":\"Move 7 of Priya's tasks due this week to Sprint 43.\",\"status\":\"proposed\",\"proposedBy\":\"anthropic:claude-haiku-5-5\",\"appliedBy\":null,\"createdAt\":\"2026-10-23T08:14:09.512Z\",\"appliedAt\":null,\"undoneAt\":null,\"cancelledAt\":null,\"ops\":[{\"id\":\"3f0d2b2a-8d5c-4b7e-9d3e-9d0f7a1c2e44\",\"position\":0,\"taskId\":\"WEB-21\",\"taskTitle\":\"Analytics events audit\",\"field\":\"sprint\",\"before\":\"sprint-42\",\"after\":\"sprint-43\",\"beforeLabel\":\"Sprint 42\",\"afterLabel\":\"Sprint 43\",\"applied\":false,\"included\":true}],\"rejected\":[],\"trimmed\":0,\"tokensIn\":2310,\"tokensOut\":180,\"costUsd\":0.000495,\"tools\":[{\"position\":0,\"name\":\"search_tasks\",\"ok\":true,\"summary\":{\"total\":7,\"shown\":7,\"ids\":[\"WEB-21\"]},\"error\":null,\"durationMs\":41}]}}}"}}},"400":{"description":"The body is not JSON, is too large (over 4 KB), or fails validation. Codes: `invalid_request`.","headers":{"X-Request-Id":{"description":"Identifies the request in the server logs. Quote it when reporting a problem.","schema":{"type":"string"}},"Server-Timing":{"description":"Milliseconds the server spent before it began to answer, as `app;dur=<ms>`. For a streamed answer, before the first byte.","schema":{"type":"string","pattern":"^app;dur=\\d+$"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"error":{"code":"invalid_request","message":"The body is not JSON, is too large (over 4 KB), or fails validation.","requestId":"0b6b1f5e-6f43-4d1e-9a64-0e3a5b9d2c11"}}}}},"403":{"description":"The request came from another site. Browsers send an Origin header; it must be this site. Codes: `forbidden_origin`.","headers":{"X-Request-Id":{"description":"Identifies the request in the server logs. Quote it when reporting a problem.","schema":{"type":"string"}},"Server-Timing":{"description":"Milliseconds the server spent before it began to answer, as `app;dur=<ms>`. For a streamed answer, before the first byte.","schema":{"type":"string","pattern":"^app;dur=\\d+$"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"error":{"code":"forbidden_origin","message":"The request came from another site. Browsers send an Origin header; it must be this site.","requestId":"0b6b1f5e-6f43-4d1e-9a64-0e3a5b9d2c11"}}}}},"429":{"description":"Too many requests from this address. Codes: `rate_limited`.","headers":{"X-Request-Id":{"description":"Identifies the request in the server logs. Quote it when reporting a problem.","schema":{"type":"string"}},"Server-Timing":{"description":"Milliseconds the server spent before it began to answer, as `app;dur=<ms>`. For a streamed answer, before the first byte.","schema":{"type":"string","pattern":"^app;dur=\\d+$"}},"Retry-After":{"description":"Seconds to wait before trying again.","schema":{"type":"string","pattern":"^\\d+$"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"error":{"code":"rate_limited","message":"Too many requests. Try again in 42 seconds.","requestId":"0b6b1f5e-6f43-4d1e-9a64-0e3a5b9d2c11","retryAfterSeconds":42}}}}},"500":{"description":"Something unexpected failed. Nothing was changed. Codes: `internal`.","headers":{"X-Request-Id":{"description":"Identifies the request in the server logs. Quote it when reporting a problem.","schema":{"type":"string"}},"Server-Timing":{"description":"Milliseconds the server spent before it began to answer, as `app;dur=<ms>`. For a streamed answer, before the first byte.","schema":{"type":"string","pattern":"^app;dur=\\d+$"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"error":{"code":"internal","message":"Something unexpected failed. Nothing was changed.","requestId":"0b6b1f5e-6f43-4d1e-9a64-0e3a5b9d2c11"}}}}},"503":{"description":"No model is configured, or the daily token budget is spent (it resets at 00:00 UTC). Codes: `unavailable`, `budget_exhausted`.","headers":{"X-Request-Id":{"description":"Identifies the request in the server logs. Quote it when reporting a problem.","schema":{"type":"string"}},"Server-Timing":{"description":"Milliseconds the server spent before it began to answer, as `app;dur=<ms>`. For a streamed answer, before the first byte.","schema":{"type":"string","pattern":"^app;dur=\\d+$"}},"Retry-After":{"description":"With `budget_exhausted`: seconds until the budget resets.","schema":{"type":"string","pattern":"^\\d+$"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"error":{"code":"budget_exhausted","message":"The assistant has used its model budget for today. It resets at 00:00 UTC.","requestId":"0b6b1f5e-6f43-4d1e-9a64-0e3a5b9d2c11","resetsAt":"2026-10-24T00:00:00.000Z"}}}}}}}},"/api/changes":{"get":{"operationId":"listChanges","tags":["Changes"],"summary":"List change sets","description":"The change log, newest first, with the number of change sets in each status (counted over all of them, whatever the filter).","parameters":[{"name":"status","in":"query","required":false,"description":"Only change sets in this status.","schema":{"type":"string","enum":["proposed","applied","undone","cancelled"]}},{"name":"limit","in":"query","required":false,"description":"At most 100. Default 50.","schema":{"type":"integer","minimum":1,"maximum":100}}],"responses":{"200":{"description":"The change sets, with counts.","headers":{"X-Request-Id":{"description":"Identifies the request in the server logs. Quote it when reporting a problem.","schema":{"type":"string"}},"Server-Timing":{"description":"Milliseconds the server spent before it began to answer, as `app;dur=<ms>`. For a streamed answer, before the first byte.","schema":{"type":"string","pattern":"^app;dur=\\d+$"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ChangesResponse"}}}},"400":{"description":"`status` or `limit` is not valid. Codes: `invalid_request`.","headers":{"X-Request-Id":{"description":"Identifies the request in the server logs. Quote it when reporting a problem.","schema":{"type":"string"}},"Server-Timing":{"description":"Milliseconds the server spent before it began to answer, as `app;dur=<ms>`. For a streamed answer, before the first byte.","schema":{"type":"string","pattern":"^app;dur=\\d+$"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"error":{"code":"invalid_request","message":"`status` or `limit` is not valid.","requestId":"0b6b1f5e-6f43-4d1e-9a64-0e3a5b9d2c11"}}}}},"500":{"description":"Something unexpected failed. Nothing was changed. Codes: `internal`.","headers":{"X-Request-Id":{"description":"Identifies the request in the server logs. Quote it when reporting a problem.","schema":{"type":"string"}},"Server-Timing":{"description":"Milliseconds the server spent before it began to answer, as `app;dur=<ms>`. For a streamed answer, before the first byte.","schema":{"type":"string","pattern":"^app;dur=\\d+$"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"error":{"code":"internal","message":"Something unexpected failed. Nothing was changed.","requestId":"0b6b1f5e-6f43-4d1e-9a64-0e3a5b9d2c11"}}}}}}}},"/api/changes/{id}":{"get":{"operationId":"getChange","tags":["Changes"],"summary":"Read one change set","description":"One change set with its diff (`ops`), what the policy refused (`rejected`), the assistant steps that led to it (`tools`), its token usage and cost.","parameters":[{"name":"id","in":"path","required":true,"description":"The change set id (a UUID).","schema":{"type":"string","format":"uuid"}}],"responses":{"200":{"description":"The change set.","headers":{"X-Request-Id":{"description":"Identifies the request in the server logs. Quote it when reporting a problem.","schema":{"type":"string"}},"Server-Timing":{"description":"Milliseconds the server spent before it began to answer, as `app;dur=<ms>`. For a streamed answer, before the first byte.","schema":{"type":"string","pattern":"^app;dur=\\d+$"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ChangeResponse"},"example":{"changeSet":{"id":"6c1a0c1e-5f0e-4d9e-9a57-2f1d3c6d9b10","prompt":"Move all of Priya's tasks due this week to next sprint","summary":"Move 7 of Priya's tasks due this week to Sprint 43.","status":"proposed","proposedBy":"anthropic:claude-haiku-5-5","appliedBy":null,"createdAt":"2026-10-23T08:14:09.512Z","appliedAt":null,"undoneAt":null,"cancelledAt":null,"ops":[{"id":"3f0d2b2a-8d5c-4b7e-9d3e-9d0f7a1c2e44","position":0,"taskId":"WEB-21","taskTitle":"Analytics events audit","field":"sprint","before":"sprint-42","after":"sprint-43","beforeLabel":"Sprint 42","afterLabel":"Sprint 43","applied":false,"included":true}],"rejected":[],"trimmed":0,"tokensIn":2310,"tokensOut":180,"costUsd":0.000495,"tools":[{"position":0,"name":"search_tasks","ok":true,"summary":{"total":7,"shown":7,"ids":["WEB-21"]},"error":null,"durationMs":41}]}}}}},"400":{"description":"The id is not a UUID. Codes: `invalid_request`.","headers":{"X-Request-Id":{"description":"Identifies the request in the server logs. Quote it when reporting a problem.","schema":{"type":"string"}},"Server-Timing":{"description":"Milliseconds the server spent before it began to answer, as `app;dur=<ms>`. For a streamed answer, before the first byte.","schema":{"type":"string","pattern":"^app;dur=\\d+$"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"error":{"code":"invalid_request","message":"The id is not a UUID.","requestId":"0b6b1f5e-6f43-4d1e-9a64-0e3a5b9d2c11"}}}}},"404":{"description":"There is no such change set. Codes: `not_found`.","headers":{"X-Request-Id":{"description":"Identifies the request in the server logs. Quote it when reporting a problem.","schema":{"type":"string"}},"Server-Timing":{"description":"Milliseconds the server spent before it began to answer, as `app;dur=<ms>`. For a streamed answer, before the first byte.","schema":{"type":"string","pattern":"^app;dur=\\d+$"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"error":{"code":"not_found","message":"There is no such change set.","requestId":"0b6b1f5e-6f43-4d1e-9a64-0e3a5b9d2c11"}}}}},"500":{"description":"Something unexpected failed. Nothing was changed. Codes: `internal`.","headers":{"X-Request-Id":{"description":"Identifies the request in the server logs. Quote it when reporting a problem.","schema":{"type":"string"}},"Server-Timing":{"description":"Milliseconds the server spent before it began to answer, as `app;dur=<ms>`. For a streamed answer, before the first byte.","schema":{"type":"string","pattern":"^app;dur=\\d+$"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"error":{"code":"internal","message":"Something unexpected failed. Nothing was changed.","requestId":"0b6b1f5e-6f43-4d1e-9a64-0e3a5b9d2c11"}}}}}}}},"/api/changes/{id}/apply":{"post":{"operationId":"applyChange","tags":["Changes"],"summary":"Apply a proposal","description":"Writes every change in the proposal in one atomic step. Each change is re-checked against what its task holds now: if any task has moved since the proposal, nothing is written and the answer is `409 stale` with the rows that changed. `exclude` leaves out the changes a person unticked; they are kept on record as not included and never touch a task. Applying twice is refused. A browser request must come from this site (the Origin header is checked); a request with no Origin, such as curl, is accepted. Writes are limited to 60 per ten minutes per address.","parameters":[{"name":"id","in":"path","required":true,"description":"The change set id (a UUID).","schema":{"type":"string","format":"uuid"}}],"requestBody":{"required":false,"description":"Optional. An empty body applies everything.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApplyRequest"},"example":{"exclude":["3f0d2b2a-8d5c-4b7e-9d3e-9d0f7a1c2e44"]}}}},"responses":{"200":{"description":"Applied. `touched` lists the tasks that were written.","headers":{"X-Request-Id":{"description":"Identifies the request in the server logs. Quote it when reporting a problem.","schema":{"type":"string"}},"Server-Timing":{"description":"Milliseconds the server spent before it began to answer, as `app;dur=<ms>`. For a streamed answer, before the first byte.","schema":{"type":"string","pattern":"^app;dur=\\d+$"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/TransitionResponse"}}}},"400":{"description":"The id is not a UUID, the body is not valid, or every change was left out. Codes: `invalid_request`.","headers":{"X-Request-Id":{"description":"Identifies the request in the server logs. Quote it when reporting a problem.","schema":{"type":"string"}},"Server-Timing":{"description":"Milliseconds the server spent before it began to answer, as `app;dur=<ms>`. For a streamed answer, before the first byte.","schema":{"type":"string","pattern":"^app;dur=\\d+$"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"error":{"code":"invalid_request","message":"The id is not a UUID, the body is not valid, or every change was left out.","requestId":"0b6b1f5e-6f43-4d1e-9a64-0e3a5b9d2c11"}}}}},"403":{"description":"The request came from another site. Browsers send an Origin header; it must be this site. Codes: `forbidden_origin`.","headers":{"X-Request-Id":{"description":"Identifies the request in the server logs. Quote it when reporting a problem.","schema":{"type":"string"}},"Server-Timing":{"description":"Milliseconds the server spent before it began to answer, as `app;dur=<ms>`. For a streamed answer, before the first byte.","schema":{"type":"string","pattern":"^app;dur=\\d+$"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"error":{"code":"forbidden_origin","message":"The request came from another site. Browsers send an Origin header; it must be this site.","requestId":"0b6b1f5e-6f43-4d1e-9a64-0e3a5b9d2c11"}}}}},"404":{"description":"There is no such change set. Codes: `not_found`.","headers":{"X-Request-Id":{"description":"Identifies the request in the server logs. Quote it when reporting a problem.","schema":{"type":"string"}},"Server-Timing":{"description":"Milliseconds the server spent before it began to answer, as `app;dur=<ms>`. For a streamed answer, before the first byte.","schema":{"type":"string","pattern":"^app;dur=\\d+$"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"error":{"code":"not_found","message":"There is no such change set.","requestId":"0b6b1f5e-6f43-4d1e-9a64-0e3a5b9d2c11"}}}}},"409":{"description":"`not_proposed`: it is already applied, undone or cancelled (the body says which). `stale`: a task changed since the proposal; nothing was written. Codes: `not_proposed`, `stale`.","headers":{"X-Request-Id":{"description":"Identifies the request in the server logs. Quote it when reporting a problem.","schema":{"type":"string"}},"Server-Timing":{"description":"Milliseconds the server spent before it began to answer, as `app;dur=<ms>`. For a streamed answer, before the first byte.","schema":{"type":"string","pattern":"^app;dur=\\d+$"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"error":{"code":"stale","message":"The board changed since this was proposed. Nothing was applied.","requestId":"0b6b1f5e-6f43-4d1e-9a64-0e3a5b9d2c11","conflicts":[{"opId":"3f0d2b2a-8d5c-4b7e-9d3e-9d0f7a1c2e44","taskId":"WEB-21","taskTitle":"Analytics events audit","field":"sprint","expected":"sprint-42","actual":"sprint-43"}]}}}}},"429":{"description":"Too many requests from this address. Codes: `rate_limited`.","headers":{"X-Request-Id":{"description":"Identifies the request in the server logs. Quote it when reporting a problem.","schema":{"type":"string"}},"Server-Timing":{"description":"Milliseconds the server spent before it began to answer, as `app;dur=<ms>`. For a streamed answer, before the first byte.","schema":{"type":"string","pattern":"^app;dur=\\d+$"}},"Retry-After":{"description":"Seconds to wait before trying again.","schema":{"type":"string","pattern":"^\\d+$"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"error":{"code":"rate_limited","message":"Too many requests. Try again in 42 seconds.","requestId":"0b6b1f5e-6f43-4d1e-9a64-0e3a5b9d2c11","retryAfterSeconds":42}}}}},"500":{"description":"Something unexpected failed. Nothing was changed. Codes: `internal`.","headers":{"X-Request-Id":{"description":"Identifies the request in the server logs. Quote it when reporting a problem.","schema":{"type":"string"}},"Server-Timing":{"description":"Milliseconds the server spent before it began to answer, as `app;dur=<ms>`. For a streamed answer, before the first byte.","schema":{"type":"string","pattern":"^app;dur=\\d+$"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"error":{"code":"internal","message":"Something unexpected failed. Nothing was changed.","requestId":"0b6b1f5e-6f43-4d1e-9a64-0e3a5b9d2c11"}}}}}}}},"/api/changes/{id}/undo":{"post":{"operationId":"undoChange","tags":["Changes"],"summary":"Undo an applied change set","description":"Puts every applied change back with its inverse, atomically and re-checked the same way: if a task was edited after the apply, nothing is undone and the answer is `409 stale`. Undoing twice is refused. There is no time limit in the demo. A browser request must come from this site (the Origin header is checked); a request with no Origin, such as curl, is accepted. Writes are limited to 60 per ten minutes per address.","parameters":[{"name":"id","in":"path","required":true,"description":"The change set id (a UUID).","schema":{"type":"string","format":"uuid"}}],"responses":{"200":{"description":"Undone. `touched` lists the tasks that were written.","headers":{"X-Request-Id":{"description":"Identifies the request in the server logs. Quote it when reporting a problem.","schema":{"type":"string"}},"Server-Timing":{"description":"Milliseconds the server spent before it began to answer, as `app;dur=<ms>`. For a streamed answer, before the first byte.","schema":{"type":"string","pattern":"^app;dur=\\d+$"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/TransitionResponse"}}}},"400":{"description":"The id is not a UUID. Codes: `invalid_request`.","headers":{"X-Request-Id":{"description":"Identifies the request in the server logs. Quote it when reporting a problem.","schema":{"type":"string"}},"Server-Timing":{"description":"Milliseconds the server spent before it began to answer, as `app;dur=<ms>`. For a streamed answer, before the first byte.","schema":{"type":"string","pattern":"^app;dur=\\d+$"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"error":{"code":"invalid_request","message":"The id is not a UUID.","requestId":"0b6b1f5e-6f43-4d1e-9a64-0e3a5b9d2c11"}}}}},"403":{"description":"The request came from another site. Browsers send an Origin header; it must be this site. Codes: `forbidden_origin`.","headers":{"X-Request-Id":{"description":"Identifies the request in the server logs. Quote it when reporting a problem.","schema":{"type":"string"}},"Server-Timing":{"description":"Milliseconds the server spent before it began to answer, as `app;dur=<ms>`. For a streamed answer, before the first byte.","schema":{"type":"string","pattern":"^app;dur=\\d+$"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"error":{"code":"forbidden_origin","message":"The request came from another site. Browsers send an Origin header; it must be this site.","requestId":"0b6b1f5e-6f43-4d1e-9a64-0e3a5b9d2c11"}}}}},"404":{"description":"There is no such change set. Codes: `not_found`.","headers":{"X-Request-Id":{"description":"Identifies the request in the server logs. Quote it when reporting a problem.","schema":{"type":"string"}},"Server-Timing":{"description":"Milliseconds the server spent before it began to answer, as `app;dur=<ms>`. For a streamed answer, before the first byte.","schema":{"type":"string","pattern":"^app;dur=\\d+$"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"error":{"code":"not_found","message":"There is no such change set.","requestId":"0b6b1f5e-6f43-4d1e-9a64-0e3a5b9d2c11"}}}}},"409":{"description":"`not_applied`: it was never applied, or is already undone (the body says which). `stale`: a task was changed after the apply; nothing was undone. Codes: `not_applied`, `stale`.","headers":{"X-Request-Id":{"description":"Identifies the request in the server logs. Quote it when reporting a problem.","schema":{"type":"string"}},"Server-Timing":{"description":"Milliseconds the server spent before it began to answer, as `app;dur=<ms>`. For a streamed answer, before the first byte.","schema":{"type":"string","pattern":"^app;dur=\\d+$"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"error":{"code":"not_applied","message":"`not_applied`: it was never applied, or is already undone (the body says which). `stale`: a task was changed after the apply; nothing was undone.","requestId":"0b6b1f5e-6f43-4d1e-9a64-0e3a5b9d2c11"}}}}},"429":{"description":"Too many requests from this address. Codes: `rate_limited`.","headers":{"X-Request-Id":{"description":"Identifies the request in the server logs. Quote it when reporting a problem.","schema":{"type":"string"}},"Server-Timing":{"description":"Milliseconds the server spent before it began to answer, as `app;dur=<ms>`. For a streamed answer, before the first byte.","schema":{"type":"string","pattern":"^app;dur=\\d+$"}},"Retry-After":{"description":"Seconds to wait before trying again.","schema":{"type":"string","pattern":"^\\d+$"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"error":{"code":"rate_limited","message":"Too many requests. Try again in 42 seconds.","requestId":"0b6b1f5e-6f43-4d1e-9a64-0e3a5b9d2c11","retryAfterSeconds":42}}}}},"500":{"description":"Something unexpected failed. Nothing was changed. Codes: `internal`.","headers":{"X-Request-Id":{"description":"Identifies the request in the server logs. Quote it when reporting a problem.","schema":{"type":"string"}},"Server-Timing":{"description":"Milliseconds the server spent before it began to answer, as `app;dur=<ms>`. For a streamed answer, before the first byte.","schema":{"type":"string","pattern":"^app;dur=\\d+$"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"error":{"code":"internal","message":"Something unexpected failed. Nothing was changed.","requestId":"0b6b1f5e-6f43-4d1e-9a64-0e3a5b9d2c11"}}}}}}}},"/api/changes/{id}/cancel":{"post":{"operationId":"cancelChange","tags":["Changes"],"summary":"Cancel a proposal","description":"Drops a proposal that has not been applied. Nothing is written to tasks. A browser request must come from this site (the Origin header is checked); a request with no Origin, such as curl, is accepted. Writes are limited to 60 per ten minutes per address.","parameters":[{"name":"id","in":"path","required":true,"description":"The change set id (a UUID).","schema":{"type":"string","format":"uuid"}}],"responses":{"200":{"description":"Cancelled.","headers":{"X-Request-Id":{"description":"Identifies the request in the server logs. Quote it when reporting a problem.","schema":{"type":"string"}},"Server-Timing":{"description":"Milliseconds the server spent before it began to answer, as `app;dur=<ms>`. For a streamed answer, before the first byte.","schema":{"type":"string","pattern":"^app;dur=\\d+$"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ChangeResponse"}}}},"400":{"description":"The id is not a UUID. Codes: `invalid_request`.","headers":{"X-Request-Id":{"description":"Identifies the request in the server logs. Quote it when reporting a problem.","schema":{"type":"string"}},"Server-Timing":{"description":"Milliseconds the server spent before it began to answer, as `app;dur=<ms>`. For a streamed answer, before the first byte.","schema":{"type":"string","pattern":"^app;dur=\\d+$"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"error":{"code":"invalid_request","message":"The id is not a UUID.","requestId":"0b6b1f5e-6f43-4d1e-9a64-0e3a5b9d2c11"}}}}},"403":{"description":"The request came from another site. Browsers send an Origin header; it must be this site. Codes: `forbidden_origin`.","headers":{"X-Request-Id":{"description":"Identifies the request in the server logs. Quote it when reporting a problem.","schema":{"type":"string"}},"Server-Timing":{"description":"Milliseconds the server spent before it began to answer, as `app;dur=<ms>`. For a streamed answer, before the first byte.","schema":{"type":"string","pattern":"^app;dur=\\d+$"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"error":{"code":"forbidden_origin","message":"The request came from another site. Browsers send an Origin header; it must be this site.","requestId":"0b6b1f5e-6f43-4d1e-9a64-0e3a5b9d2c11"}}}}},"404":{"description":"There is no such change set. Codes: `not_found`.","headers":{"X-Request-Id":{"description":"Identifies the request in the server logs. Quote it when reporting a problem.","schema":{"type":"string"}},"Server-Timing":{"description":"Milliseconds the server spent before it began to answer, as `app;dur=<ms>`. For a streamed answer, before the first byte.","schema":{"type":"string","pattern":"^app;dur=\\d+$"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"error":{"code":"not_found","message":"There is no such change set.","requestId":"0b6b1f5e-6f43-4d1e-9a64-0e3a5b9d2c11"}}}}},"409":{"description":"It is already applied, undone or cancelled (the body says which). Codes: `not_proposed`.","headers":{"X-Request-Id":{"description":"Identifies the request in the server logs. Quote it when reporting a problem.","schema":{"type":"string"}},"Server-Timing":{"description":"Milliseconds the server spent before it began to answer, as `app;dur=<ms>`. For a streamed answer, before the first byte.","schema":{"type":"string","pattern":"^app;dur=\\d+$"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"error":{"code":"not_proposed","message":"It is already applied, undone or cancelled (the body says which).","requestId":"0b6b1f5e-6f43-4d1e-9a64-0e3a5b9d2c11"}}}}},"429":{"description":"Too many requests from this address. Codes: `rate_limited`.","headers":{"X-Request-Id":{"description":"Identifies the request in the server logs. Quote it when reporting a problem.","schema":{"type":"string"}},"Server-Timing":{"description":"Milliseconds the server spent before it began to answer, as `app;dur=<ms>`. For a streamed answer, before the first byte.","schema":{"type":"string","pattern":"^app;dur=\\d+$"}},"Retry-After":{"description":"Seconds to wait before trying again.","schema":{"type":"string","pattern":"^\\d+$"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"error":{"code":"rate_limited","message":"Too many requests. Try again in 42 seconds.","requestId":"0b6b1f5e-6f43-4d1e-9a64-0e3a5b9d2c11","retryAfterSeconds":42}}}}},"500":{"description":"Something unexpected failed. Nothing was changed. Codes: `internal`.","headers":{"X-Request-Id":{"description":"Identifies the request in the server logs. Quote it when reporting a problem.","schema":{"type":"string"}},"Server-Timing":{"description":"Milliseconds the server spent before it began to answer, as `app;dur=<ms>`. For a streamed answer, before the first byte.","schema":{"type":"string","pattern":"^app;dur=\\d+$"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"error":{"code":"internal","message":"Something unexpected failed. Nothing was changed.","requestId":"0b6b1f5e-6f43-4d1e-9a64-0e3a5b9d2c11"}}}}}}}},"/api/board":{"get":{"operationId":"getBoard","tags":["Workspace"],"summary":"Read the board","description":"Projects, sprints, people with their workload, and every task. The board page polls this every 20 seconds.","responses":{"200":{"description":"The whole workspace.","headers":{"X-Request-Id":{"description":"Identifies the request in the server logs. Quote it when reporting a problem.","schema":{"type":"string"}},"Server-Timing":{"description":"Milliseconds the server spent before it began to answer, as `app;dur=<ms>`. For a streamed answer, before the first byte.","schema":{"type":"string","pattern":"^app;dur=\\d+$"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Board"}}}},"500":{"description":"Something unexpected failed. Nothing was changed. Codes: `internal`.","headers":{"X-Request-Id":{"description":"Identifies the request in the server logs. Quote it when reporting a problem.","schema":{"type":"string"}},"Server-Timing":{"description":"Milliseconds the server spent before it began to answer, as `app;dur=<ms>`. For a streamed answer, before the first byte.","schema":{"type":"string","pattern":"^app;dur=\\d+$"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"error":{"code":"internal","message":"Something unexpected failed. Nothing was changed.","requestId":"0b6b1f5e-6f43-4d1e-9a64-0e3a5b9d2c11"}}}}}}}},"/api/people":{"get":{"operationId":"listPeople","tags":["Workspace"],"summary":"List people","description":"The team, active people first. `workload` is open tasks in the active sprint, against `capacity`. Someone with `active: false` has left the team and cannot be assigned work.","responses":{"200":{"description":"The team.","headers":{"X-Request-Id":{"description":"Identifies the request in the server logs. Quote it when reporting a problem.","schema":{"type":"string"}},"Server-Timing":{"description":"Milliseconds the server spent before it began to answer, as `app;dur=<ms>`. For a streamed answer, before the first byte.","schema":{"type":"string","pattern":"^app;dur=\\d+$"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/PeopleResponse"}}}},"500":{"description":"Something unexpected failed. Nothing was changed. Codes: `internal`.","headers":{"X-Request-Id":{"description":"Identifies the request in the server logs. Quote it when reporting a problem.","schema":{"type":"string"}},"Server-Timing":{"description":"Milliseconds the server spent before it began to answer, as `app;dur=<ms>`. For a streamed answer, before the first byte.","schema":{"type":"string","pattern":"^app;dur=\\d+$"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"error":{"code":"internal","message":"Something unexpected failed. Nothing was changed.","requestId":"0b6b1f5e-6f43-4d1e-9a64-0e3a5b9d2c11"}}}}}}}},"/api/sprints":{"get":{"operationId":"listSprints","tags":["Workspace"],"summary":"List sprints","description":"Every sprint, oldest first. Only sprints that are not `closed` can take tasks.","responses":{"200":{"description":"The sprints.","headers":{"X-Request-Id":{"description":"Identifies the request in the server logs. Quote it when reporting a problem.","schema":{"type":"string"}},"Server-Timing":{"description":"Milliseconds the server spent before it began to answer, as `app;dur=<ms>`. For a streamed answer, before the first byte.","schema":{"type":"string","pattern":"^app;dur=\\d+$"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/SprintsResponse"}}}},"500":{"description":"Something unexpected failed. Nothing was changed. Codes: `internal`.","headers":{"X-Request-Id":{"description":"Identifies the request in the server logs. Quote it when reporting a problem.","schema":{"type":"string"}},"Server-Timing":{"description":"Milliseconds the server spent before it began to answer, as `app;dur=<ms>`. For a streamed answer, before the first byte.","schema":{"type":"string","pattern":"^app;dur=\\d+$"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"error":{"code":"internal","message":"Something unexpected failed. Nothing was changed.","requestId":"0b6b1f5e-6f43-4d1e-9a64-0e3a5b9d2c11"}}}}}}}},"/api/cron/reseed":{"get":{"operationId":"reseed","tags":["Platform"],"summary":"Reseed the demo workspace","description":"Puts the shared demo back to its fixtures. Vercel Cron calls it nightly with `Authorization: Bearer <CRON_SECRET>`; it is not for visitors. The token budget, rate limits and eval history are not touched. Without a configured secret the route is closed (503), never open. At most 6 calls an hour.","responses":{"200":{"description":"The workspace was rebuilt.","headers":{"X-Request-Id":{"description":"Identifies the request in the server logs. Quote it when reporting a problem.","schema":{"type":"string"}},"Server-Timing":{"description":"Milliseconds the server spent before it began to answer, as `app;dur=<ms>`. For a streamed answer, before the first byte.","schema":{"type":"string","pattern":"^app;dur=\\d+$"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ReseedResponse"}}}},"401":{"description":"No bearer secret, or the wrong one. Codes: `unauthorized`.","headers":{"X-Request-Id":{"description":"Identifies the request in the server logs. Quote it when reporting a problem.","schema":{"type":"string"}},"Server-Timing":{"description":"Milliseconds the server spent before it began to answer, as `app;dur=<ms>`. For a streamed answer, before the first byte.","schema":{"type":"string","pattern":"^app;dur=\\d+$"}},"WWW-Authenticate":{"description":"Always `Bearer realm=\"cron\"`.","schema":{"type":"string"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"error":{"code":"unauthorized","message":"No bearer secret, or the wrong one.","requestId":"0b6b1f5e-6f43-4d1e-9a64-0e3a5b9d2c11"}}}}},"429":{"description":"Too many requests from this address. Codes: `rate_limited`.","headers":{"X-Request-Id":{"description":"Identifies the request in the server logs. Quote it when reporting a problem.","schema":{"type":"string"}},"Server-Timing":{"description":"Milliseconds the server spent before it began to answer, as `app;dur=<ms>`. For a streamed answer, before the first byte.","schema":{"type":"string","pattern":"^app;dur=\\d+$"}},"Retry-After":{"description":"Seconds to wait before trying again.","schema":{"type":"string","pattern":"^\\d+$"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"error":{"code":"rate_limited","message":"Too many requests. Try again in 42 seconds.","requestId":"0b6b1f5e-6f43-4d1e-9a64-0e3a5b9d2c11","retryAfterSeconds":42}}}}},"500":{"description":"The reseed failed twice. The previous workspace is untouched. Codes: `internal`.","headers":{"X-Request-Id":{"description":"Identifies the request in the server logs. Quote it when reporting a problem.","schema":{"type":"string"}},"Server-Timing":{"description":"Milliseconds the server spent before it began to answer, as `app;dur=<ms>`. For a streamed answer, before the first byte.","schema":{"type":"string","pattern":"^app;dur=\\d+$"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"error":{"code":"internal","message":"The reseed failed twice. The previous workspace is untouched.","requestId":"0b6b1f5e-6f43-4d1e-9a64-0e3a5b9d2c11"}}}}},"503":{"description":"The deployment has no CRON_SECRET, so the route is closed. Codes: `not_configured`.","headers":{"X-Request-Id":{"description":"Identifies the request in the server logs. Quote it when reporting a problem.","schema":{"type":"string"}},"Server-Timing":{"description":"Milliseconds the server spent before it began to answer, as `app;dur=<ms>`. For a streamed answer, before the first byte.","schema":{"type":"string","pattern":"^app;dur=\\d+$"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"error":{"code":"not_configured","message":"The deployment has no CRON_SECRET, so the route is closed.","requestId":"0b6b1f5e-6f43-4d1e-9a64-0e3a5b9d2c11"}}}}}},"security":[{"cronBearer":[]}]},"post":{"operationId":"reseedPost","tags":["Platform"],"summary":"Reseed the demo workspace (POST)","description":"Puts the shared demo back to its fixtures. Vercel Cron calls it nightly with `Authorization: Bearer <CRON_SECRET>`; it is not for visitors. The token budget, rate limits and eval history are not touched. Without a configured secret the route is closed (503), never open. At most 6 calls an hour.","responses":{"200":{"description":"The workspace was rebuilt.","headers":{"X-Request-Id":{"description":"Identifies the request in the server logs. Quote it when reporting a problem.","schema":{"type":"string"}},"Server-Timing":{"description":"Milliseconds the server spent before it began to answer, as `app;dur=<ms>`. For a streamed answer, before the first byte.","schema":{"type":"string","pattern":"^app;dur=\\d+$"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ReseedResponse"}}}},"401":{"description":"No bearer secret, or the wrong one. Codes: `unauthorized`.","headers":{"X-Request-Id":{"description":"Identifies the request in the server logs. Quote it when reporting a problem.","schema":{"type":"string"}},"Server-Timing":{"description":"Milliseconds the server spent before it began to answer, as `app;dur=<ms>`. For a streamed answer, before the first byte.","schema":{"type":"string","pattern":"^app;dur=\\d+$"}},"WWW-Authenticate":{"description":"Always `Bearer realm=\"cron\"`.","schema":{"type":"string"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"error":{"code":"unauthorized","message":"No bearer secret, or the wrong one.","requestId":"0b6b1f5e-6f43-4d1e-9a64-0e3a5b9d2c11"}}}}},"429":{"description":"Too many requests from this address. Codes: `rate_limited`.","headers":{"X-Request-Id":{"description":"Identifies the request in the server logs. Quote it when reporting a problem.","schema":{"type":"string"}},"Server-Timing":{"description":"Milliseconds the server spent before it began to answer, as `app;dur=<ms>`. For a streamed answer, before the first byte.","schema":{"type":"string","pattern":"^app;dur=\\d+$"}},"Retry-After":{"description":"Seconds to wait before trying again.","schema":{"type":"string","pattern":"^\\d+$"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"error":{"code":"rate_limited","message":"Too many requests. Try again in 42 seconds.","requestId":"0b6b1f5e-6f43-4d1e-9a64-0e3a5b9d2c11","retryAfterSeconds":42}}}}},"500":{"description":"The reseed failed twice. The previous workspace is untouched. Codes: `internal`.","headers":{"X-Request-Id":{"description":"Identifies the request in the server logs. Quote it when reporting a problem.","schema":{"type":"string"}},"Server-Timing":{"description":"Milliseconds the server spent before it began to answer, as `app;dur=<ms>`. For a streamed answer, before the first byte.","schema":{"type":"string","pattern":"^app;dur=\\d+$"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"error":{"code":"internal","message":"The reseed failed twice. The previous workspace is untouched.","requestId":"0b6b1f5e-6f43-4d1e-9a64-0e3a5b9d2c11"}}}}},"503":{"description":"The deployment has no CRON_SECRET, so the route is closed. Codes: `not_configured`.","headers":{"X-Request-Id":{"description":"Identifies the request in the server logs. Quote it when reporting a problem.","schema":{"type":"string"}},"Server-Timing":{"description":"Milliseconds the server spent before it began to answer, as `app;dur=<ms>`. For a streamed answer, before the first byte.","schema":{"type":"string","pattern":"^app;dur=\\d+$"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"error":{"code":"not_configured","message":"The deployment has no CRON_SECRET, so the route is closed.","requestId":"0b6b1f5e-6f43-4d1e-9a64-0e3a5b9d2c11"}}}}}},"security":[{"cronBearer":[]}]}},"/api/health":{"get":{"operationId":"health","tags":["Platform"],"summary":"Liveness probe","description":"Answers without touching the database, so a probe never costs a query.","responses":{"200":{"description":"The server is up.","headers":{"X-Request-Id":{"description":"Identifies the request in the server logs. Quote it when reporting a problem.","schema":{"type":"string"}},"Server-Timing":{"description":"Milliseconds the server spent before it began to answer, as `app;dur=<ms>`. For a streamed answer, before the first byte.","schema":{"type":"string","pattern":"^app;dur=\\d+$"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Health"},"example":{"ok":true}}}}}}},"/api/openapi.json":{"get":{"operationId":"openapi","tags":["Platform"],"summary":"This document","description":"The OpenAPI 3.1 document, generated from the same Zod schemas the routes validate with. Readable from any origin.","responses":{"200":{"description":"The document.","headers":{"X-Request-Id":{"description":"Identifies the request in the server logs. Quote it when reporting a problem.","schema":{"type":"string"}},"Server-Timing":{"description":"Milliseconds the server spent before it began to answer, as `app;dur=<ms>`. For a streamed answer, before the first byte.","schema":{"type":"string","pattern":"^app;dur=\\d+$"}}},"content":{"application/json":{"schema":{"type":"object","propertyNames":{"type":"string"},"additionalProperties":{}}}}}}}},"/api/docs":{"get":{"operationId":"docs","tags":["Platform"],"summary":"This reference, as a page","description":"The same document rendered on the server as plain HTML. It runs no script.","responses":{"200":{"description":"An HTML page.","headers":{"X-Request-Id":{"description":"Identifies the request in the server logs. Quote it when reporting a problem.","schema":{"type":"string"}},"Server-Timing":{"description":"Milliseconds the server spent before it began to answer, as `app;dur=<ms>`. For a streamed answer, before the first byte.","schema":{"type":"string","pattern":"^app;dur=\\d+$"}}},"content":{"text/html":{"schema":{"type":"string"}}}}}}}},"components":{"schemas":{"Person":{"type":"object","properties":{"id":{"type":"string","description":"A short readable id such as \"priya\"."},"name":{"type":"string"},"initials":{"type":"string"},"role":{"type":"string"},"active":{"type":"boolean","description":"False for someone who has left the team and cannot take work."},"capacity":{"type":"integer","description":"Open tasks in the active sprint that make a full plate."},"workload":{"type":"integer","description":"Open tasks (todo, doing, waiting) in the active sprint."}},"required":["id","name","initials","role","active","capacity","workload"],"additionalProperties":false},"Sprint":{"type":"object","properties":{"id":{"type":"string","description":"For example \"sprint-42\"."},"name":{"type":"string"},"startsOn":{"type":"string","pattern":"^\\d{4}-\\d{2}-\\d{2}$"},"endsOn":{"type":"string","pattern":"^\\d{4}-\\d{2}-\\d{2}$"},"state":{"type":"string","enum":["closed","active","planned"]}},"required":["id","name","startsOn","endsOn","state"],"additionalProperties":false},"Project":{"type":"object","properties":{"id":{"type":"string"},"key":{"type":"string","description":"The prefix of task ids, for example \"WEB\"."},"name":{"type":"string"}},"required":["id","key","name"],"additionalProperties":false},"Task":{"type":"object","properties":{"id":{"type":"string","description":"For example \"WEB-21\"."},"projectId":{"type":"string"},"title":{"type":"string"},"status":{"type":"string","enum":["todo","doing","waiting","done","archived"]},"priority":{"type":"string","enum":["low","medium","high","urgent"]},"dueDate":{"anyOf":[{"type":"string","pattern":"^\\d{4}-\\d{2}-\\d{2}$"},{"type":"null"}]},"assigneeId":{"type":["string","null"]},"sprintId":{"description":"Null means the backlog.","type":["string","null"]},"labels":{"type":"array","items":{"type":"string"}},"comments":{"type":"integer"},"createdAt":{"type":"string","format":"date-time"}},"required":["id","projectId","title","status","priority","dueDate","assigneeId","sprintId","labels","comments","createdAt"],"additionalProperties":false},"Board":{"type":"object","properties":{"today":{"type":"string","pattern":"^\\d{4}-\\d{2}-\\d{2}$","description":"The board's own \"today\" (UTC)."},"projects":{"type":"array","items":{"$ref":"#/components/schemas/Project"}},"sprints":{"type":"array","items":{"$ref":"#/components/schemas/Sprint"}},"people":{"type":"array","items":{"$ref":"#/components/schemas/Person"}},"tasks":{"type":"array","items":{"$ref":"#/components/schemas/Task"}}},"required":["today","projects","sprints","people","tasks"],"additionalProperties":false},"Rejection":{"type":"object","properties":{"taskId":{"type":"string"},"field":{"type":"string"},"code":{"type":"string","enum":["unknown_task","field_not_allowed","invalid_value","no_change","duplicate","task_archived","archive_requires_done","due_out_of_range","assignee_unavailable","sprint_unavailable","cap_exceeded","no_delete"]},"message":{"type":"string","description":"A plain sentence saying why the policy refused this change."}},"required":["taskId","field","code","message"],"additionalProperties":false},"ChangeOp":{"type":"object","properties":{"id":{"type":"string","format":"uuid"},"position":{"type":"integer"},"taskId":{"type":"string"},"taskTitle":{"type":"string"},"field":{"type":"string","enum":["status","priority","due","assignee","sprint"]},"before":{"description":"What the task held when the proposal was made.","type":["string","null"]},"after":{"description":"What the proposal would set.","type":["string","null"]},"beforeLabel":{"description":"A person or sprint name for an id; otherwise the value.","type":["string","null"]},"afterLabel":{"type":["string","null"]},"applied":{"type":"boolean","description":"True while `after` is what the task holds."},"included":{"type":"boolean","description":"False when the person unticked the change before applying."}},"required":["id","position","taskId","taskTitle","field","before","after","beforeLabel","afterLabel","applied","included"],"additionalProperties":false},"ToolCall":{"type":"object","properties":{"position":{"type":"integer"},"name":{"type":"string"},"ok":{"type":"boolean"},"summary":{"description":"Ids, counts and statuses, never whole result bodies."},"error":{"type":["string","null"]},"durationMs":{"type":"integer"}},"required":["position","name","ok","summary","error","durationMs"],"additionalProperties":false},"ChangeSet":{"type":"object","properties":{"id":{"type":"string","format":"uuid"},"prompt":{"type":"string","description":"The request as stored, with contact details removed."},"summary":{"type":"string"},"status":{"type":"string","enum":["proposed","applied","undone","cancelled"]},"proposedBy":{"type":"string","description":"`provider:model` that proposed it."},"appliedBy":{"type":["string","null"]},"createdAt":{"type":"string","format":"date-time"},"appliedAt":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}]},"undoneAt":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}]},"cancelledAt":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}]},"ops":{"type":"array","items":{"$ref":"#/components/schemas/ChangeOp"}},"rejected":{"type":"array","items":{"$ref":"#/components/schemas/Rejection"},"description":"Changes the policy refused, with reasons."},"trimmed":{"type":"integer","description":"Valid changes cut for being over the 25-change cap."},"tokensIn":{"type":"integer"},"tokensOut":{"type":"integer"},"costUsd":{"type":"number"},"tools":{"type":"array","items":{"$ref":"#/components/schemas/ToolCall"}}},"required":["id","prompt","summary","status","proposedBy","appliedBy","createdAt","appliedAt","undoneAt","cancelledAt","ops","rejected","trimmed","tokensIn","tokensOut","costUsd","tools"],"additionalProperties":false},"Conflict":{"type":"object","properties":{"opId":{"type":"string","format":"uuid"},"taskId":{"type":"string"},"taskTitle":{"type":"string"},"field":{"type":"string","enum":["status","priority","due","assignee","sprint"]},"expected":{"description":"What the proposal (or undo) expected the task to hold.","type":["string","null"]},"actual":{"description":"What the task holds now.","type":["string","null"]}},"required":["opId","taskId","taskTitle","field","expected","actual"],"additionalProperties":false},"Clarification":{"type":"object","properties":{"question":{"type":"string"},"options":{"type":"array","items":{"type":"object","properties":{"label":{"type":"string"},"value":{"type":"string"}},"required":["label","value"],"additionalProperties":false}}},"required":["question","options"],"additionalProperties":false},"AskResult":{"oneOf":[{"type":"object","properties":{"kind":{"type":"string","const":"proposal"},"changeSet":{"$ref":"#/components/schemas/ChangeSet"}},"required":["kind","changeSet"],"additionalProperties":false},{"type":"object","properties":{"kind":{"type":"string","const":"clarification"},"clarification":{"$ref":"#/components/schemas/Clarification"}},"required":["kind","clarification"],"additionalProperties":false},{"type":"object","properties":{"kind":{"type":"string","const":"refusal"},"message":{"type":"string"},"reasons":{"type":"array","items":{"$ref":"#/components/schemas/Rejection"}}},"required":["kind","message","reasons"],"additionalProperties":false},{"type":"object","properties":{"kind":{"type":"string","const":"answer"},"message":{"type":"string"}},"required":["kind","message"],"additionalProperties":false}]},"AskEvent":{"oneOf":[{"type":"object","properties":{"type":{"type":"string","const":"activity"},"tool":{"type":"string"},"label":{"type":"string","description":"What the assistant is doing, for example \"Searching tasks\"."}},"required":["type","tool","label"],"additionalProperties":false},{"type":"object","properties":{"type":{"type":"string","const":"result"},"askId":{"type":"string","format":"uuid"},"result":{"$ref":"#/components/schemas/AskResult"}},"required":["type","askId","result"],"additionalProperties":false},{"type":"object","properties":{"type":{"type":"string","const":"error"},"code":{"type":"string","enum":["unavailable","internal"]},"message":{"type":"string"}},"required":["type","code","message"],"additionalProperties":false}]},"ChangeCounts":{"type":"object","properties":{"proposed":{"type":"integer"},"applied":{"type":"integer"},"undone":{"type":"integer"},"cancelled":{"type":"integer"}},"required":["proposed","applied","undone","cancelled"],"additionalProperties":false},"Error":{"type":"object","properties":{"error":{"type":"object","properties":{"code":{"type":"string","enum":["invalid_request","forbidden_origin","unauthorized","not_found","not_proposed","not_applied","stale","rate_limited","budget_exhausted","unavailable","not_configured","internal"],"description":"Machine-readable. Branch on this, never on the message."},"message":{"type":"string","description":"Safe to show to a person."},"requestId":{"type":"string","description":"Also sent as the X-Request-Id header; quote it in a report."},"status":{"description":"With not_proposed and not_applied: the status the change set is in.","type":"string","enum":["proposed","applied","undone","cancelled"]},"conflicts":{"description":"With stale: the rows that no longer match. Nothing was written.","type":"array","items":{"$ref":"#/components/schemas/Conflict"}},"retryAfterSeconds":{"description":"With rate_limited.","type":"integer"},"resetsAt":{"description":"With budget_exhausted: the next 00:00 UTC.","type":"string","format":"date-time"}},"required":["code","message","requestId"],"additionalProperties":false}},"required":["error"],"additionalProperties":false},"Health":{"type":"object","properties":{"ok":{"type":"boolean","const":true}},"required":["ok"],"additionalProperties":false},"PeopleResponse":{"type":"object","properties":{"people":{"type":"array","items":{"$ref":"#/components/schemas/Person"}}},"required":["people"],"additionalProperties":false},"SprintsResponse":{"type":"object","properties":{"sprints":{"type":"array","items":{"$ref":"#/components/schemas/Sprint"}}},"required":["sprints"],"additionalProperties":false},"ChangesResponse":{"type":"object","properties":{"changes":{"type":"array","items":{"$ref":"#/components/schemas/ChangeSet"}},"counts":{"description":"Change sets per status, whatever the filter.","$ref":"#/components/schemas/ChangeCounts"}},"required":["changes","counts"],"additionalProperties":false},"ChangeResponse":{"type":"object","properties":{"changeSet":{"$ref":"#/components/schemas/ChangeSet"}},"required":["changeSet"],"additionalProperties":false},"TransitionResponse":{"type":"object","properties":{"changeSet":{"$ref":"#/components/schemas/ChangeSet"},"touched":{"type":"array","items":{"type":"string"},"description":"Ids of the tasks that were written."}},"required":["changeSet","touched"],"additionalProperties":false},"ReseedResponse":{"type":"object","properties":{"ok":{"type":"boolean","const":true},"seeded":{"type":"object","properties":{"projects":{"type":"integer"},"sprints":{"type":"integer"},"people":{"type":"integer"},"tasks":{"type":"integer"},"comments":{"type":"integer"}},"required":["projects","sprints","people","tasks","comments"],"additionalProperties":false},"ms":{"type":"integer"}},"required":["ok","seeded","ms"],"additionalProperties":false},"AskRequest":{"type":"object","properties":{"prompt":{"type":"string","minLength":2,"maxLength":500},"followUp":{"description":"The option the person picked after the assistant asked a question.","type":"object","properties":{"question":{"type":"string","minLength":1,"maxLength":200},"label":{"type":"string","minLength":1,"maxLength":120},"value":{"type":"string","minLength":1,"maxLength":60}},"required":["question","label","value"]}},"required":["prompt"]},"ApplyRequest":{"type":"object","properties":{"exclude":{"description":"Ids of changes to leave out (the unticked rows). They are never written.","maxItems":25,"type":"array","items":{"type":"string","format":"uuid"}}},"additionalProperties":false}},"securitySchemes":{"cronBearer":{"type":"http","scheme":"bearer","description":"The deployment's CRON_SECRET. Vercel Cron sends it; visitors do not have it."}}}}