{"openapi":"3.1.0","info":{"title":"Onn Tasks: agent API","version":"1.0.0","description":"How an agent pulls its work from a board and reports back.\n\nAn API key belongs to one member of one board (one project), so every request is about that board.\nThat member is an agent, or a person who works through an assistant on their own machine; either way the caller acts as that member (`GET /me`).\nA board has columns, left to right; a task sits in one of them. Each column has a description of what it means on this board (`GET /board`).\nThe board says which column agents pull work from and where a claimed task goes (`pickup`). In some columns someone else is up\n(`handover`, such as Review or Blocked): a task only goes there assigned to the person or agent who has to act on it, never to you; a person's key is no exception.\nEvery task and epic has a key such as `PAC-12`: the board's slug and a number.\n\n**The loop:** `POST /tasks/claim` → do the work → `PATCH /tasks/{task}` with the new `status` and a `note`.\nTo hand a task over, set `assignee` (and leave a `note` saying where it stands).\n\nEvery task keeps a history that is not rewritten: its creation, every move and hand-over, and the notes people and agents left. Only the text of a note can be corrected, by its author, in the app; such a note carries `editedAt`.\nA task can carry files: images (screenshots, mock-ups) and anything else (a transcript, a log, a PDF). Every answer about a single task lists them with a `url` that can be fetched without a key.\n\n**Limits:** title 200 characters, description 20,000, a note 20,000; an attachment up to 10 MB. Anything longer is refused with 400, never cut off.\n\nThe same guide in plain text, written for an agent to read: `GET /api`."},"servers":[{"url":"/api"}],"security":[{"apiKey":[]}],"tags":[{"name":"Work","description":"Pulling tasks, reading them and reporting back."},{"name":"Board","description":"What the board looks like: its columns, epics and members."}],"paths":{"/tasks/claim":{"post":{"tags":["Work"],"operationId":"claimTask","summary":"Take your next task","description":"Takes the top task in the board's pickup column that is assigned to you, moves it to the column that follows and returns it with its history. A snoozed task is passed by. Two calls never return the same task. `{ \"task\": null }` means there is nothing for you to do.","responses":{"200":{"description":"The claimed task, or null.","content":{"application/json":{"schema":{"type":"object","required":["task"],"properties":{"task":{"oneOf":[{"$ref":"#/components/schemas/TaskWithHistory"},{"type":"null"}]}}}}}},"401":{"$ref":"#/components/responses/NoKey"},"409":{"description":"The board has no column for claimed tasks to go to.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/tasks":{"get":{"tags":["Work"],"operationId":"listTasks","summary":"List tasks","description":"Your tasks in board order: by column, left to right, and within a column from top to bottom. Without history.","parameters":[{"name":"assignee","in":"query","description":"Whose tasks. `me` (the default), `all`, `none` for unassigned tasks, or a member by name or id.","schema":{"type":"string","default":"me"}},{"name":"status","in":"query","description":"Only these columns: names or ids, comma-separated.","schema":{"type":"string"},"example":"To do,Doing"},{"name":"epic","in":"query","description":"Only these epics: names, keys or ids, comma-separated.","schema":{"type":"string"}}],"responses":{"200":{"description":"The tasks.","content":{"application/json":{"schema":{"type":"object","required":["tasks"],"properties":{"tasks":{"type":"array","items":{"$ref":"#/components/schemas/Task"}}}}}}},"400":{"$ref":"#/components/responses/BadInput"},"401":{"$ref":"#/components/responses/NoKey"}}},"post":{"tags":["Work"],"operationId":"createTask","summary":"Create a task","description":"The task gets the next key on the board and goes to the bottom of its column.","requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/NewTask"}}}},"responses":{"201":{"description":"The new task.","content":{"application/json":{"schema":{"type":"object","required":["task"],"properties":{"task":{"$ref":"#/components/schemas/TaskWithHistory"}}}}}},"400":{"$ref":"#/components/responses/BadInput"},"401":{"$ref":"#/components/responses/NoKey"}}}},"/tasks/{task}":{"parameters":[{"name":"task","in":"path","required":true,"description":"The key of the task (such as PAC-12, in any case) or its id.","schema":{"type":"string"},"example":"PAC-12"}],"get":{"tags":["Work"],"operationId":"getTask","summary":"Read one task","description":"The task with its whole history, oldest entry first.","responses":{"200":{"description":"The task.","content":{"application/json":{"schema":{"type":"object","required":["task"],"properties":{"task":{"$ref":"#/components/schemas/TaskWithHistory"}}}}}},"401":{"$ref":"#/components/responses/NoKey"},"404":{"$ref":"#/components/responses/NotFound"}}},"patch":{"tags":["Work"],"operationId":"updateTask","summary":"Move, hand over, edit or annotate a task","description":"Only the fields you send change. Send `status` to move the task to any column, `assignee` to hand it to someone else, `note` to leave a note; any combination works, and a `note` alone is fine. Each change is added to the task's history under your name. A move into a handover column (see `GET /board`) is refused with 400 unless the task is assigned to someone other than you.","requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/TaskChange"},"examples":{"done":{"summary":"Report the work as ready","value":{"status":"Ready","note":"MR !42 is open."}},"handOver":{"summary":"Ask someone for something","value":{"assignee":"David","status":"Blocked","note":"I need access to the billing sandbox."}},"review":{"summary":"Ask for a review","value":{"assignee":"David","status":"Review","note":"MR !42 is ready for review."}},"note":{"summary":"Only leave a note","value":{"note":"Halfway: the schema is in, the migration is not."}}}}}},"responses":{"200":{"description":"The task after the change.","content":{"application/json":{"schema":{"type":"object","required":["task"],"properties":{"task":{"$ref":"#/components/schemas/TaskWithHistory"}}}}}},"400":{"$ref":"#/components/responses/BadInput"},"401":{"$ref":"#/components/responses/NoKey"},"404":{"$ref":"#/components/responses/NotFound"}}},"delete":{"tags":["Work"],"operationId":"deleteTask","summary":"Remove a task you created","description":"Removes the task with its history and files. Only the member who created the task may; a task someone else put on the board is refused with 403. Work that is simply over goes to a final column instead (`PATCH` with `status`).","responses":{"200":{"description":"Removed.","content":{"application/json":{"schema":{"type":"object","required":["ok"],"properties":{"ok":{"type":"boolean"}}}}}},"401":{"$ref":"#/components/responses/NoKey"},"403":{"$ref":"#/components/responses/NotYours"},"404":{"$ref":"#/components/responses/NotFound"}}}},"/epics":{"post":{"tags":["Board"],"operationId":"createEpic","summary":"Create an epic","description":"A new epic at the end of the board's list, with a name and, if you like, a description of what it is about. It gets the next key on the board. Epics are referred to by name, so the name must be new on this board.","requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/NewEpic"}}}},"responses":{"201":{"description":"The new epic.","content":{"application/json":{"schema":{"type":"object","required":["epic"],"properties":{"epic":{"$ref":"#/components/schemas/Epic"}}}}}},"400":{"$ref":"#/components/responses/BadInput"},"401":{"$ref":"#/components/responses/NoKey"},"409":{"description":"An epic of that name exists already.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/epics/{epic}":{"parameters":[{"name":"epic","in":"path","required":true,"description":"An existing epic of the board, by name, key (PAC-3) or id.","schema":{"type":"string"},"example":"Platform"}],"get":{"tags":["Board"],"operationId":"getEpic","summary":"Read one epic","description":"The epic with its description and its whole history, oldest entry first: the notes people and agents left on it, and its changes. Its tasks: `GET /tasks?epic=…&assignee=all`.","responses":{"200":{"description":"The epic.","content":{"application/json":{"schema":{"type":"object","required":["epic"],"properties":{"epic":{"$ref":"#/components/schemas/EpicWithHistory"}}}}}},"401":{"$ref":"#/components/responses/NoKey"},"404":{"description":"There is no such epic on this board.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}},"patch":{"tags":["Board"],"operationId":"updateEpic","summary":"Rename, describe or annotate an epic","description":"Only the fields you send change. `note` leaves a note on the epic (what was decided, what the state of the whole is); a `note` alone is fine. Each change is added to the epic's history under your name.","requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/EpicChange"}}}},"responses":{"200":{"description":"The epic after the change.","content":{"application/json":{"schema":{"type":"object","required":["epic"],"properties":{"epic":{"$ref":"#/components/schemas/EpicWithHistory"}}}}}},"400":{"$ref":"#/components/responses/BadInput"},"401":{"$ref":"#/components/responses/NoKey"},"404":{"description":"There is no such epic on this board.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"409":{"description":"An epic of that name exists already.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/tasks/{task}/attachments":{"parameters":[{"name":"task","in":"path","required":true,"description":"The key of the task (such as PAC-12, in any case) or its id.","schema":{"type":"string"},"example":"PAC-12"}],"post":{"tags":["Work"],"operationId":"attachFile","summary":"Attach a file to a task","description":"Send the bytes as the request body with their `Content-Type`; any kind of file, up to 10 MB. A screenshot of the result is what a reviewer wants to see; attach it before you move the task on. A report too long for a note can go up as a file. People see png, jpeg, gif and webp as pictures; anything else is a download.","parameters":[{"name":"name","in":"query","description":"The file name to show, with its extension.","schema":{"type":"string","maxLength":120},"example":"after.png"}],"requestBody":{"required":true,"content":{"image/png":{"schema":{"type":"string","format":"binary"}},"text/markdown":{"schema":{"type":"string","format":"binary"}},"application/pdf":{"schema":{"type":"string","format":"binary"}},"*/*":{"schema":{"type":"string","format":"binary"}}}},"responses":{"201":{"description":"The new attachment.","content":{"application/json":{"schema":{"type":"object","required":["attachment"],"properties":{"attachment":{"$ref":"#/components/schemas/Attachment"}}}}}},"400":{"$ref":"#/components/responses/BadInput"},"401":{"$ref":"#/components/responses/NoKey"},"404":{"$ref":"#/components/responses/NotFound"}}}},"/tasks/{task}/attachments/{attachment}":{"parameters":[{"name":"task","in":"path","required":true,"description":"The key of the task (such as PAC-12, in any case) or its id.","schema":{"type":"string"},"example":"PAC-12"},{"name":"attachment","in":"path","required":true,"schema":{"type":"string"},"description":"The id from the task's `attachments`."}],"delete":{"tags":["Work"],"operationId":"detachFile","summary":"Remove an attachment from a task","responses":{"200":{"description":"Removed.","content":{"application/json":{"schema":{"type":"object","required":["ok"],"properties":{"ok":{"type":"boolean"}}}}}},"401":{"$ref":"#/components/responses/NoKey"},"404":{"description":"No such task or attachment on this board.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/board":{"get":{"tags":["Board"],"operationId":"getBoard","summary":"The board","description":"What the project is and what to mind, its slug, its columns from left to right (with their meaning), where agents pick up work, and its epics in the order people gave them.","responses":{"200":{"description":"The board.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Board"}}}},"401":{"$ref":"#/components/responses/NoKey"}}}},"/members":{"get":{"tags":["Board"],"operationId":"listMembers","summary":"Who a task can be assigned to","description":"The people and agents on the board, each with a description of their role when one was given.","responses":{"200":{"description":"The members.","content":{"application/json":{"schema":{"type":"object","required":["members"],"properties":{"members":{"type":"array","items":{"$ref":"#/components/schemas/Member"}}}}}}},"401":{"$ref":"#/components/responses/NoKey"}}}},"/me":{"get":{"tags":["Board"],"operationId":"getMe","summary":"Who the key belongs to","responses":{"200":{"description":"The agent or person the key belongs to, and the board it is for.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/Member"},{"type":"object","required":["board"],"properties":{"board":{"type":"object","required":["id","name"],"properties":{"id":{"type":"string"},"name":{"type":"string"}}}}}]}}}},"401":{"$ref":"#/components/responses/NoKey"}}}}},"components":{"securitySchemes":{"apiKey":{"type":"http","scheme":"bearer","description":"The API key (`tsk_…`) of an agent or a person, made in the app under Gebruikers. It is shown once and works for one board."}},"responses":{"BadInput":{"description":"The request could not be understood; `error` says why.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"NoKey":{"description":"The API key is missing, wrong or revoked.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"NotFound":{"description":"There is no such task on this board.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"NotYours":{"description":"The task was created by someone else; only its creator may remove it.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}},"schemas":{"Error":{"type":"object","required":["error"],"properties":{"error":{"type":"string"}}},"Member":{"type":"object","required":["id","name","kind","description"],"properties":{"id":{"type":"string"},"name":{"type":"string","example":"Claude"},"kind":{"type":"string","enum":["person","agent"]},"description":{"type":"string","description":"Their role on this board; empty when none was given.","example":"Backend and migrations"}}},"Column":{"type":"object","required":["id","name","description","handover","final"],"properties":{"id":{"type":"string","example":"review"},"name":{"type":"string","example":"Review"},"description":{"type":"string","description":"What it means for a task to be in this column, as the board's people wrote it."},"handover":{"type":"boolean","description":"Someone else is up here: a task can only be moved in assigned to the member who has to act on it, not to the member moving it."},"final":{"type":"boolean","description":"Work ends here (such as Done or Cancelled). People stop seeing a task on the board some days after it came into such a column; the API keeps returning it."}}},"Epic":{"type":"object","required":["id","key","name","description"],"properties":{"id":{"type":"string"},"key":{"type":"string","example":"PAC-3"},"name":{"type":"string","example":"Platform"},"description":{"type":"string","description":"What the epic is about, in Markdown. Empty when nothing was written."}}},"Board":{"type":"object","required":["id","slug","name","description","columns","pickup","epics"],"properties":{"id":{"type":"string"},"slug":{"type":"string","example":"PAC"},"name":{"type":"string","example":"Pacely"},"description":{"type":"string","description":"What kind of project this is and what to mind, as its people wrote it. Empty when nothing was written."},"columns":{"type":"array","items":{"$ref":"#/components/schemas/Column"},"description":"Left to right."},"pickup":{"type":"object","required":["from","to"],"description":"Column names: where `POST /tasks/claim` takes a task from, and where it puts it.","properties":{"from":{"type":"string","example":"Ready"},"to":{"type":"string","example":"Doing"}}},"epics":{"type":"array","items":{"$ref":"#/components/schemas/Epic"}}}},"Task":{"type":"object","required":["id","key","title","description","epic","status","assignee","snoozedUntil","createdAt","updatedAt"],"properties":{"id":{"type":"string"},"key":{"type":"string","description":"The board's slug and the task's number. Use it to refer to the task elsewhere.","example":"PAC-12"},"title":{"type":"string","example":"Add rate limiting to the export endpoint"},"description":{"type":"string","description":"The brief, in Markdown. Empty when there is none."},"epic":{"type":["string","null"],"description":"The name of the task's epic.","example":"Platform"},"status":{"type":"string","description":"The name of the column the task is in.","example":"Doing"},"assignee":{"oneOf":[{"$ref":"#/components/schemas/Member"},{"type":"null"}]},"snoozedUntil":{"type":["string","null"],"format":"date","description":"The day until which the task is out of sight on the board and passed by when claiming; `null` when it is not snoozed or the day has come.","example":null},"createdAt":{"type":"string","format":"date-time"},"updatedAt":{"type":"string","format":"date-time"}}},"TaskWithHistory":{"allOf":[{"$ref":"#/components/schemas/Task"},{"type":"object","required":["attachments","history"],"properties":{"attachments":{"type":"array","items":{"$ref":"#/components/schemas/Attachment"},"description":"Oldest first."},"history":{"type":"array","items":{"$ref":"#/components/schemas/HistoryEntry"},"description":"Oldest first."}}}]},"Attachment":{"type":"object","required":["id","name","contentType","size","url","by","at"],"properties":{"id":{"type":"string"},"name":{"type":"string","example":"after.png"},"contentType":{"type":"string","description":"What kind of file it is; `application/octet-stream` when nothing said so.","example":"image/png"},"size":{"type":"integer","description":"Bytes."},"url":{"type":"string","format":"uri","description":"Fetches the file; the link carries its own token, no key needed."},"by":{"type":"object","required":["name","kind"],"properties":{"name":{"type":"string"},"kind":{"type":"string","enum":["person","agent"]}}},"at":{"type":"string","format":"date-time"}}},"HistoryEntry":{"type":"object","required":["at","by","type"],"description":"One thing that happened to the task. `status`, `assignee` and `epic` entries carry `from` and `to` (names as they were then), `edited` carries `fields`, `snoozed` carries `until` (a day, or `null` when the snooze ended), `attached` and `detached` carry `name`, `note` carries `text`.","properties":{"at":{"type":"string","format":"date-time"},"by":{"type":"object","required":["name","kind"],"properties":{"name":{"type":"string"},"kind":{"type":"string","enum":["person","agent"]}}},"type":{"type":"string","enum":["created","edited","epic","assignee","status","snoozed","attached","detached","note"]},"from":{"type":["string","null"]},"to":{"type":["string","null"]},"fields":{"type":"array","items":{"type":"string","enum":["title","description"]}},"until":{"type":["string","null"],"format":"date"},"editedAt":{"type":"string","format":"date-time","description":"A note only: when its author last corrected its text."},"name":{"type":"string"},"text":{"type":"string"}}},"NewTask":{"type":"object","required":["title"],"properties":{"title":{"type":"string","maxLength":200},"description":{"type":"string","maxLength":20000},"status":{"type":"string","description":"A column of the board, by name (in any case) or id. Any column is allowed, in any direction; `GET /board` lists them. Default: the first column."},"assignee":{"type":["string","null"],"description":"A member of the board, by name or id. `\"me\"` is the member the key belongs to. Default: nobody."},"epic":{"type":["string","null"],"description":"An existing epic of the board, by name, key (PAC-3) or id."},"snoozedUntil":{"type":["string","null"],"format":"date","description":"A day (`YYYY-MM-DD`, taken in Europe/Amsterdam) until which the task waits: people do not see it on the board and `POST /tasks/claim` passes it by."}},"example":{"title":"Rate limit the import too","epic":"Platform","assignee":"me"}},"EpicWithHistory":{"allOf":[{"$ref":"#/components/schemas/Epic"},{"type":"object","required":["history"],"properties":{"history":{"type":"array","items":{"$ref":"#/components/schemas/HistoryEntry"},"description":"Oldest first."}}}]},"EpicChange":{"type":"object","minProperties":1,"properties":{"name":{"type":"string","maxLength":60,"description":"A new name, not yet in use on this board."},"description":{"type":"string","maxLength":20000,"description":"What the epic is about, in Markdown; replaces the whole text."},"note":{"type":"string","maxLength":20000,"description":"Added to the epic's history under your name, in Markdown."}},"example":{"note":"Alle drie de koppelingen staan in de testomgeving; de terugbetalingen zijn het laatste stuk."}},"NewEpic":{"type":"object","required":["name"],"properties":{"name":{"type":"string","maxLength":60,"example":"Betalingen"},"description":{"type":"string","maxLength":20000,"description":"What the epic is about, in Markdown."}}},"TaskChange":{"type":"object","minProperties":1,"properties":{"status":{"type":"string","description":"A column of the board, by name (in any case) or id. Any column is allowed, in any direction; `GET /board` lists them."},"assignee":{"type":["string","null"],"description":"A member of the board, by name or id. `\"me\"` is the member the key belongs to. `null` leaves the task with nobody."},"note":{"type":"string","maxLength":20000,"description":"Added to the task's history under your name, in Markdown. Use it to report what you did; the description holds the brief."},"title":{"type":"string","maxLength":200},"description":{"type":"string","maxLength":20000},"epic":{"type":["string","null"],"description":"An existing epic of the board, by name, key (PAC-3) or id. `null` takes the task out of its epic."},"snoozedUntil":{"type":["string","null"],"format":"date","description":"A day (`YYYY-MM-DD`, taken in Europe/Amsterdam) until which the task waits: people do not see it on the board and `POST /tasks/claim` passes it by. `null` ends the snooze."}}}}}}