{"openapi":"3.1.0","info":{"title":"Taskeristl API","version":"0.1.0","description":"Centralized task tracking API for people and agents.\nCRUD over tasks with an owner (the person or system the\ntask belongs to), optional assignee, labels, metadata,\nand status (open, in_progress, waiting, deferred, closed).\nAuthenticated with a static API key passed in the `X-API-Key` header.\n\n## Workflows\n\nCanonical filter combinations callers compose from primitives.\nSubstitute `ME` with the user's identifier (e.g. `armando.arias`).\n\n| Workflow          | What it answers                                                       | Filters                                                        |\n| ----------------- | --------------------------------------------------------------------- | -------------------------------------------------------------- |\n| `active`          | Open or in-progress tasks assigned to me.                             | `status=open,in_progress&assignee=ME`                          |\n| `mine`            | Everything on my plate (open + in_progress + waiting), assigned to me.| `status=open,in_progress,waiting&assignee=ME`                  |\n| `triage`          | Open tasks I own that nobody is doing yet.                            | `status=open&owner=ME&assignee_is_null=true`                   |\n| `waiting`         | Tasks I own that are blocked on a third party.                        | `status=waiting&owner=ME`                                      |\n| `on_others`       | Open, in-progress, or waiting tasks I own that someone else is doing. | `status=open,in_progress,waiting&owner=ME&assignee_not=ME`     |\n| `closed`          | Everything I've completed.                                            | `status=closed&owner=ME`                                       |\n| `recently_closed` | Closed tasks I own from the last 3 workdays (caller computes cutoff). | `status=closed&owner=ME&closed_after=<ISO>`                    |\n| `deferred`        | Tasks created for me but not mine to do.                              | `status=deferred&owner=ME`                                     |\n| `starred`         | Tasks I own that I've flagged as important.                           | `is_starred=true&owner=ME`                                     |\n\nThe server has no concept of workdays or holidays — for\n`recently_closed`, the caller computes the ISO cutoff and passes it\nas `closed_after`.\n"},"servers":[{"url":"http://localhost:3000","description":"Local development"}],"security":[{"ApiKeyAuth":[]}],"paths":{"/healthz":{"get":{"summary":"Health check","security":[],"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"type":"object","properties":{"status":{"type":"string","example":"ok"}}}}}}}}},"/v1/tasks":{"get":{"summary":"List tasks","description":"List tasks ordered by `last_updated_at` descending. Filters AND together.\nSee the [Workflows](#tag/Workflows) section for canonical combinations.\n","parameters":[{"in":"query","name":"status","description":"Filter by lifecycle state. Accepts a single value (`status=open`),\nmultiple repeated values (`status=open&status=waiting`), or a\ncomma-separated string (`status=open,waiting`).\n","schema":{"oneOf":[{"$ref":"#/components/schemas/Status"},{"type":"array","items":{"$ref":"#/components/schemas/Status"}},{"type":"string","description":"Comma-separated list of statuses."}]},"style":"form","explode":true},{"in":"query","name":"owner","schema":{"type":"string"}},{"in":"query","name":"assignee","schema":{"type":"string"}},{"in":"query","name":"assignee_not","description":"Exclude tasks whose `assignee` equals this value. Tasks with a\nNULL `assignee` are also excluded (standard SQL three-valued\nlogic). Useful for the `on_others` workflow.\n","schema":{"type":"string"}},{"in":"query","name":"assignee_is_null","description":"`true` returns only unassigned tasks; `false` returns only\nassigned tasks. Omit to ignore the assignee state.\n","schema":{"type":"boolean"}},{"in":"query","name":"customer","description":"Filter to tasks belonging to this customer (exact match).","schema":{"type":"string"}},{"in":"query","name":"label","description":"Filter to tasks whose `labels` array contains this value.","schema":{"type":"string"}},{"in":"query","name":"is_meeting","description":"Filter by the meeting flag. `true` returns only meetings, `false`\nreturns only non-meeting tasks. Omit to return both.\n","schema":{"type":"boolean"}},{"in":"query","name":"is_starred","description":"Filter by the starred flag. `true` returns only starred tasks,\n`false` returns only non-starred tasks. Omit to return both.\n","schema":{"type":"boolean"}},{"in":"query","name":"closed_after","description":"Inclusive lower bound on `closed_at` (ISO-8601). NULLs excluded.","schema":{"type":"string","format":"date-time"}},{"in":"query","name":"closed_before","description":"Exclusive upper bound on `closed_at` (ISO-8601). NULLs excluded.","schema":{"type":"string","format":"date-time"}},{"in":"query","name":"updated_after","description":"Inclusive lower bound on `last_updated_at` (ISO-8601).","schema":{"type":"string","format":"date-time"}},{"in":"query","name":"updated_before","description":"Exclusive upper bound on `last_updated_at` (ISO-8601).","schema":{"type":"string","format":"date-time"}},{"in":"query","name":"checked_after","description":"Inclusive lower bound on `last_checked_at` (ISO-8601). NULLs excluded.","schema":{"type":"string","format":"date-time"}},{"in":"query","name":"checked_before","description":"Exclusive upper bound on `last_checked_at` (ISO-8601). NULLs excluded.","schema":{"type":"string","format":"date-time"}},{"in":"query","name":"limit","schema":{"type":"integer","minimum":1,"maximum":200,"default":50}},{"in":"query","name":"offset","schema":{"type":"integer","minimum":0,"default":0}}],"responses":{"200":{"description":"List of tasks","content":{"application/json":{"schema":{"$ref":"#/components/schemas/TaskList"}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"}}},"post":{"summary":"Create a task","requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/TaskCreate"}}}},"responses":{"201":{"description":"Task created","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Task"}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"}}}},"/v1/tasks/{id}":{"parameters":[{"in":"path","name":"id","required":true,"schema":{"type":"integer","format":"int64","minimum":1}}],"get":{"summary":"Get a task by id","responses":{"200":{"description":"Task","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Task"}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"404":{"$ref":"#/components/responses/NotFound"}}},"patch":{"summary":"Update a task","requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/TaskUpdate"}}}},"responses":{"200":{"description":"Updated task","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Task"}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"404":{"$ref":"#/components/responses/NotFound"}}},"delete":{"summary":"Delete a task","responses":{"204":{"description":"Deleted"},"401":{"$ref":"#/components/responses/Unauthorized"},"404":{"$ref":"#/components/responses/NotFound"}}}},"/v1/tasks/{id}/touch":{"parameters":[{"in":"path","name":"id","required":true,"schema":{"type":"integer","format":"int64","minimum":1}}],"post":{"summary":"Touch a task (bump `last_checked_at`)","description":"Records that an agent or process reviewed this task without changing\nits content. Sets `last_checked_at` to now. Does **not** bump\n`last_updated_at`. Idempotent in effect; calling it twice in a row\njust refreshes the timestamp.\n","responses":{"200":{"description":"Updated task","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Task"}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"404":{"$ref":"#/components/responses/NotFound"}}}},"/v1/tasks/{id}/subtasks":{"parameters":[{"in":"path","name":"id","required":true,"schema":{"type":"integer","format":"int64","minimum":1}}],"get":{"summary":"List a task's subtasks","description":"Subtasks ordered by `seq` ascending.","responses":{"200":{"description":"List of subtasks","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SubtaskList"}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"404":{"$ref":"#/components/responses/NotFound"}}},"post":{"summary":"Create a subtask","description":"Adds a subtask to the task. `seq` is assigned automatically as the next\nper-parent number (1, 2, 3…); the response includes a `name` like `#43-2`.\n","requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/SubtaskCreate"}}}},"responses":{"201":{"description":"Subtask created","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Subtask"}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"404":{"$ref":"#/components/responses/NotFound"}}}},"/v1/tasks/{id}/subtasks/{seq}":{"parameters":[{"in":"path","name":"id","required":true,"schema":{"type":"integer","format":"int64","minimum":1}},{"in":"path","name":"seq","required":true,"description":"Per-parent subtask number (the N in `#<task>-<N>`).","schema":{"type":"integer","minimum":1}}],"get":{"summary":"Get a subtask","responses":{"200":{"description":"Subtask","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Subtask"}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"404":{"$ref":"#/components/responses/NotFound"}}},"patch":{"summary":"Update a subtask","requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/SubtaskUpdate"}}}},"responses":{"200":{"description":"Updated subtask","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Subtask"}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"404":{"$ref":"#/components/responses/NotFound"}}},"delete":{"summary":"Delete a subtask","responses":{"204":{"description":"Deleted"},"401":{"$ref":"#/components/responses/Unauthorized"},"404":{"$ref":"#/components/responses/NotFound"}}}}},"components":{"securitySchemes":{"ApiKeyAuth":{"type":"apiKey","in":"header","name":"X-API-Key"}},"schemas":{"Status":{"type":"string","enum":["open","in_progress","waiting","deferred","closed"],"description":"Task lifecycle state.\n  - `open` — ready to be worked on; no progress yet.\n  - `in_progress` — some work has been started.\n  - `waiting` — blocked on action from a third party.\n  - `closed` — successfully completed.\n  - `deferred` — created for the owner but does not belong to them; no work needs to be done or tracked on it.\n"},"Task":{"type":"object","required":["id","title","owner","status","labels","metadata","is_meeting","is_starred","hours","customer","created_at","last_updated_at","closed_at","last_checked_at"],"properties":{"id":{"type":"integer","format":"int64","readOnly":true},"title":{"type":"string","minLength":1,"maxLength":500},"owner":{"type":"string","minLength":1,"maxLength":255,"description":"Identifies the person or system the task belongs to. Typically the\nlocal part of an email (e.g. `armando.arias`). Required.\n"},"description":{"type":["string","null"]},"status":{"$ref":"#/components/schemas/Status"},"assignee":{"type":["string","null"],"maxLength":255,"description":"Optional, free-text. Who is currently working on the task. Distinct\nfrom `owner`, which is who the task belongs to.\n"},"labels":{"type":"array","items":{"type":"string","maxLength":100},"maxItems":64},"metadata":{"type":"object","additionalProperties":true},"notes":{"type":["string","null"]},"is_meeting":{"type":"boolean","default":false,"description":"True if this task represents a meeting rather than a unit of\nwork. Defaults to false. Use the `is_meeting` query parameter\non list endpoints to filter meetings in or out.\n"},"is_starred":{"type":"boolean","default":false,"description":"True if this task has been starred (flagged as important).\nDefaults to false. Combine with `owner=ME` to drive a personal\n\"starred\" view (see the `starred` workflow). Use the\n`is_starred` query parameter on list endpoints to filter.\n"},"hours":{"type":["number","null"],"minimum":0,"description":"Estimated units of effort to complete the task, in hours.\nFree-form; the API does not interpret it. Null when unknown.\n"},"customer":{"type":["string","null"],"maxLength":255,"description":"Free-text customer this task is for. Null when internal or\nunspecified. Use the `customer` query parameter to filter\n(exact match).\n"},"created_at":{"type":"string","format":"date-time","readOnly":true},"last_updated_at":{"type":"string","format":"date-time","readOnly":true,"description":"Bumped on any content change. Not bumped by `/touch`."},"closed_at":{"type":["string","null"],"format":"date-time","description":"Auto-stamped to \"now\" when `status` transitions to `closed` and\ncleared on any transition away. Writable on create/update for\nbackdating: pass an explicit ISO-8601 value to record a\nretroactive close. Must be ≤ now (no future-dating). Cannot be\nset on a task whose effective status is not `closed`, and\ncannot be `null` when status is `closed`.\n"},"last_checked_at":{"type":["string","null"],"format":"date-time","description":"Bumped to \"now\" by `POST /v1/tasks/{id}/touch` (or the MCP\n`touch_task` tool). Writable on create/update for backdating\na review timestamp; must be ≤ now. Unlike `/touch`, setting\nit via `PATCH` also bumps `last_updated_at`.\n"}}},"TaskCreate":{"type":"object","required":["title","owner"],"additionalProperties":false,"properties":{"title":{"type":"string","minLength":1,"maxLength":500},"owner":{"type":"string","minLength":1,"maxLength":255},"description":{"type":["string","null"]},"status":{"allOf":[{"$ref":"#/components/schemas/Status"}],"default":"open"},"assignee":{"type":["string","null"],"maxLength":255},"labels":{"type":"array","items":{"type":"string","maxLength":100},"maxItems":64,"default":[]},"metadata":{"type":"object","additionalProperties":true,"default":{}},"notes":{"type":["string","null"]},"is_meeting":{"type":"boolean","default":false},"is_starred":{"type":"boolean","default":false},"hours":{"type":["number","null"],"minimum":0},"customer":{"type":["string","null"],"maxLength":255},"closed_at":{"type":["string","null"],"format":"date-time","description":"Backdate the close. Required to be ≤ now. Only allowed when\n`status` is `closed`; otherwise rejected with 400. If omitted\nand `status=closed`, the server stamps to now (existing\nbehaviour).\n"},"last_checked_at":{"type":["string","null"],"format":"date-time","description":"Backdate the last-checked timestamp. Must be ≤ now. Omit (or\nnull) to leave unset on a fresh task.\n"}}},"TaskUpdate":{"type":"object","minProperties":1,"additionalProperties":false,"properties":{"title":{"type":"string","minLength":1,"maxLength":500},"owner":{"type":"string","minLength":1,"maxLength":255},"description":{"type":["string","null"]},"status":{"$ref":"#/components/schemas/Status"},"assignee":{"type":["string","null"],"maxLength":255},"labels":{"type":"array","items":{"type":"string","maxLength":100},"maxItems":64},"metadata":{"type":"object","additionalProperties":true},"notes":{"type":["string","null"]},"is_meeting":{"type":"boolean"},"is_starred":{"type":"boolean"},"hours":{"type":["number","null"],"minimum":0},"customer":{"type":["string","null"],"maxLength":255},"closed_at":{"type":["string","null"],"format":"date-time","description":"Backdate or correct the close timestamp. Must be ≤ now (no\nfuture-dating). Only meaningful when the task's effective\nstatus (after this PATCH) is `closed`; otherwise rejected.\n`null` is only valid when the effective status is non-closed\n(and is redundant — the server clears it automatically).\n"},"last_checked_at":{"type":["string","null"],"format":"date-time","description":"Set or clear the last-checked timestamp explicitly. Must be\n≤ now (no future-dating). Pass `null` to clear. Bumps\n`last_updated_at` (unlike `/touch`).\n"}}},"TaskList":{"type":"object","required":["data","pagination"],"properties":{"data":{"type":"array","items":{"$ref":"#/components/schemas/Task"}},"pagination":{"type":"object","required":["limit","offset","total"],"properties":{"limit":{"type":"integer"},"offset":{"type":"integer"},"total":{"type":"integer"}}}}},"SubtaskStatus":{"type":"string","enum":["open","in_progress","done"],"description":"Subtask lifecycle state.\n  - `open` — not started.\n  - `in_progress` — being worked on.\n  - `done` — finished (stamps `closed_at`).\n"},"Subtask":{"type":"object","required":["task_id","seq","name","title","status","created_at","last_updated_at","closed_at"],"properties":{"task_id":{"type":"integer","format":"int64","readOnly":true,"description":"Parent task id."},"seq":{"type":"integer","readOnly":true,"description":"Per-parent sequence number (the N in `#<task>-<N>`). Stable; not reused after deletion."},"name":{"type":"string","readOnly":true,"description":"Display identifier `#<task_id>-<seq>`, e.g. `#43-2`."},"title":{"type":"string","minLength":1,"maxLength":500},"status":{"$ref":"#/components/schemas/SubtaskStatus"},"created_at":{"type":"string","format":"date-time","readOnly":true},"last_updated_at":{"type":"string","format":"date-time","readOnly":true},"closed_at":{"type":["string","null"],"format":"date-time","description":"Auto-stamped to \"now\" when `status` becomes `done`, cleared on any\ntransition away. Writable for backdating: pass an explicit ISO-8601\nvalue (≤ now) only when status is `done`.\n"}}},"SubtaskCreate":{"type":"object","required":["title"],"additionalProperties":false,"properties":{"title":{"type":"string","minLength":1,"maxLength":500},"status":{"allOf":[{"$ref":"#/components/schemas/SubtaskStatus"}],"default":"open"},"closed_at":{"type":["string","null"],"format":"date-time","description":"Backdate the close. Must be ≤ now. Only allowed when `status` is `done`."}}},"SubtaskUpdate":{"type":"object","minProperties":1,"additionalProperties":false,"properties":{"title":{"type":"string","minLength":1,"maxLength":500},"status":{"$ref":"#/components/schemas/SubtaskStatus"},"closed_at":{"type":["string","null"],"format":"date-time","description":"Backdate or correct the close timestamp (≤ now). Only valid when the\neffective status after this PATCH is `done`; `null` only when not `done`.\n"}}},"SubtaskList":{"type":"object","required":["data"],"properties":{"data":{"type":"array","items":{"$ref":"#/components/schemas/Subtask"}}}},"Error":{"type":"object","required":["error"],"properties":{"error":{"type":"object","required":["code","message"],"properties":{"code":{"type":"string"},"message":{"type":"string"},"details":{"type":"array","items":{"type":"object"}}}}}}},"responses":{"BadRequest":{"description":"Validation error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"Unauthorized":{"description":"Missing or invalid API key","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"NotFound":{"description":"Resource not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}}