{"openapi":"3.1.0","info":{"title":"coLab API","version":"1","summary":"Project, task, note, decision and chat work, as a person.","description":"`/api/v1` lets a script, a terminal or an agent do the work the app does — list,\ncreate, change, advance and delete — **as a person**, reaching exactly what that person can reach.\n\n## Authentication\n\nEvery endpoint except the two device-flow ones takes a bearer token:\n\n```bash\ncurl https://colab.neurasense.io/api/v1/tasks?assignee=me&status=todo \\\n  -H \"Authorization: Bearer colab_pat_…\"\n```\n\nGet one of two ways. **By hand**, from Account → API tokens: name it, choose read or write, choose\nan expiry, and optionally pin it to one workspace. It is shown once and only its SHA-256 is stored.\n**From a terminal**, with the device flow below — the better one, because the secret is never\ndisplayed, retyped, or left in shell history.\n\nCreating a token either way asks you to prove who you are again, with a passkey where one is\nenrolled and your password where none is. A thirty-day-old session cookie is the right bar for\nmoving a task and the wrong one for minting a credential that works from anywhere until revoked.\n\n## The device flow\n\nModelled on the OAuth device authorization grant ([RFC 8628](https://www.rfc-editor.org/rfc/rfc8628)),\nso a client written against any other device flow already knows what to do with it.\n\n```bash\n# 1. ask to be let in, and start polling\ncurl -X POST https://colab.neurasense.io/api/v1/device/code \\\n  -H 'Content-Type: application/json' \\\n  -d '{\"client_name\":\"my integration\",\"scope\":\"write\"}'\n# → { \"user_code\": \"WDJB-MJHT\", \"verification_uri_complete\": \"https://colab.neurasense.io/link?code=WDJB-MJHT\", … }\n\n# 2. the person opens that URL, sees what is asking, and approves it with a passkey\n# 3. the next poll is answered with a token\ncurl -X POST https://colab.neurasense.io/api/v1/device/token \\\n  -H 'Content-Type: application/json' -d '{\"device_code\":\"…\"}'\n```\n\nPoll no faster than the `interval` you were given (5s). Pending polls answer\n`400 authorization_pending`; polling too fast answers `429 slow_down`. A code expires after\n10 minutes, and buys exactly one token.\n\n## What a token cannot do\n\n- **Reach further than its owner.** Every request goes through the same permission check the\n  browser does. `write` scope on a project you cannot open is still a 404.\n- **Leave its workspace**, if one was pinned — answered with the same 404 as a project that does\n  not exist, because the pin is a confinement rather than a hint about what lives elsewhere.\n- **Widen anything.** Scope only narrows: a `read` token is refused a write with\n  `403 insufficient_scope`, and `write` grants nothing its owner did not already have.\n\n## Conventions\n\n- **Lists page with a cursor**, not an offset — rows get created while a client is walking a list,\n  and an offset would quietly skip one each time. Follow `next_cursor` until it is null.\n- **On a PATCH, omitted is not null.** A field left out is left alone; a field sent as `null` is\n  cleared.\n- **Dates are calendar days** (`YYYY-MM-DD`); timestamps are ISO 8601 instants.\n- **Failures are always** `{\"error\": {\"code\": …, \"message\": …}}`. Switch on `code`. A token that\n  never existed, one that was revoked and one that expired all answer `401 invalid_token` — which\n  of the three is not something the holder of a stolen token should learn from us."},"servers":[{"url":"https://colab.neurasense.io/api/v1"}],"security":[{"bearerAuth":[]}],"tags":[{"name":"Device flow","description":"Pairing a client that has no browser of its own."},{"name":"Identity","description":"Who a token is."},{"name":"Search","description":"One query across everything a token can reach."},{"name":"Projects"},{"name":"Tasks"},{"name":"Milestones"},{"name":"Decisions"},{"name":"Notes"},{"name":"Previews","description":"Builds under review, and what people pinned on them."},{"name":"Chat and agents"},{"name":"Notifications","description":"The inbox: what is addressed at you, and the ambient rest."}],"paths":{"/device/code":{"post":{"tags":["Device flow"],"summary":"Ask to be let in","description":"Unauthenticated by necessity — the caller has no credential yet. What comes back is a pending request that stays worthless until somebody signed in approves it.","security":[],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"client_name":{"type":"string","maxLength":60,"description":"Shown to the person approving. Say what and where you are."},"scope":{"type":"string","enum":["read","write"],"default":"read"}}}}}},"responses":{"201":{"description":"A pending pairing.","content":{"application/json":{"schema":{"type":"object","properties":{"device_code":{"type":"string","description":"Secret. Poll with this."},"user_code":{"type":"string","description":"Show this to the person."},"verification_uri":{"type":"string","format":"uri"},"verification_uri_complete":{"type":"string","format":"uri","description":"The same page with the code filled in, if you can open a browser."},"expires_in":{"type":"integer","description":"Seconds. 10 minutes."},"interval":{"type":"integer","description":"Seconds to wait between polls."}}}}}},"422":{"description":"`scope` was not `read` or `write`.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/device/token":{"post":{"tags":["Device flow"],"summary":"Poll for the token","description":"Answered with a token once somebody has approved the pairing, and until then with a reason to keep waiting. A pending poll is a 400 with a body rather than a 200, which is what RFC 8628 specifies and what stops a client treating 'not yet' as success. A device code buys exactly one token.","security":[],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["device_code"],"properties":{"device_code":{"type":"string"}}}}}},"responses":{"200":{"description":"Approved. Store the token; it is not shown again.","content":{"application/json":{"schema":{"type":"object","properties":{"access_token":{"type":"string"},"token_type":{"type":"string","enum":["Bearer"]},"scope":{"type":"string","enum":["read","write"]}}}}}},"400":{"description":"`authorization_pending` — keep polling. `access_denied`, `expired_token` or `invalid_grant` — stop.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Polling faster than 5s. Back off; `Retry-After` says how long.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/me":{"get":{"tags":["Identity"],"summary":"Who this token is","description":"The endpoint to call first. It confirms a pairing worked, and hands back a workspace id so a client can create things without an operator digging one out of a URL.","responses":{"200":{"description":"The token, its owner, and the workspaces in reach.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Me"}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"422":{"$ref":"#/components/responses/Unprocessable"}}}},"/search":{"get":{"tags":["Search"],"summary":"Search everything","description":"One query across projects, tasks, notes, milestones, decisions, comments, chat messages, attachments, links, previews and review pins — everything the token's owner can reach. This is the same search the app's own ⌘K palette runs.\n\nA leading `@Name` narrows to one person's work, and `@me` is whoever the token belongs to. Wrapping the query in double quotes means *exactly this*, and suppresses spelling correction. `project_id` confines the search to one accessible project; `kinds=note` makes it a note-only content search.\n\nNot paged: this is a bounded, ranked set rather than a walk through a table, and a cursor over relevance would be a cursor over an order that moves.","parameters":[{"name":"q","in":"query","required":true,"schema":{"type":"string"},"description":"What to look for. Supports a leading `@Name` or `@me`, and \"exact phrases\"."},{"name":"workspace_id","in":"query","schema":{"type":"string"},"description":"Needed only when you belong to more than one and the token is not pinned."},{"name":"project_id","in":"query","schema":{"type":"string"},"description":"Optionally confine results to one accessible project."},{"name":"kinds","in":"query","schema":{"type":"string"},"description":"Optional comma-separated filter: project, task, note, milestone, decision, comment, message, attachment, link, preview, pin."},{"name":"limit_per_kind","in":"query","schema":{"type":"integer","minimum":1,"maximum":50,"default":8},"description":"Maximum hits returned for each kind."}],"responses":{"200":{"description":"Ranked hits, most relevant first.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SearchResult"}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"422":{"$ref":"#/components/responses/Unprocessable"}}}},"/projects":{"get":{"tags":["Projects"],"summary":"List projects","parameters":[{"name":"workspace_id","in":"query","schema":{"type":"string"}},{"name":"visibility","in":"query","schema":{"type":"string","enum":["private","public"]}},{"name":"priority","in":"query","schema":{"type":"string","enum":["low","medium","high"]}},{"name":"include_archived","in":"query","schema":{"type":"boolean"}},{"name":"limit","in":"query","schema":{"type":"integer","minimum":1,"maximum":200,"default":50},"description":"How many rows to return."},{"name":"cursor","in":"query","schema":{"type":"string"},"description":"The `next_cursor` from the previous page."}],"responses":{"200":{"description":"Newest first.","content":{"application/json":{"schema":{"type":"object","properties":{"data":{"type":"array","items":{"$ref":"#/components/schemas/Project"}},"next_cursor":{"type":"string","nullable":true,"description":"Pass back as `cursor`. Null when the list is exhausted."}}}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"422":{"$ref":"#/components/responses/Unprocessable"}}},"post":{"tags":["Projects"],"summary":"Create a project","description":"`workspace_id` is needed only when you belong to more than one and the token is not pinned — otherwise there is exactly one right answer and asking would be pedantry.","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["name"],"properties":{"name":{"type":"string"},"description":{"type":"string"},"color":{"type":"string","enum":["blue","cyan","violet","emerald","amber","rose"],"default":"blue"},"visibility":{"type":"string","enum":["private","public"],"default":"private"},"priority":{"type":"string","enum":["low","medium","high"],"default":"medium"},"due_date":{"type":"string","format":"date","nullable":true},"workspace_id":{"type":"string"}}}}}},"responses":{"201":{"description":"The new project.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Project"}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"422":{"$ref":"#/components/responses/Unprocessable"}}}},"/projects/{id}":{"get":{"tags":["Projects"],"summary":"One project, and what you may do with it","parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"},"description":"Project id."}],"responses":{"200":{"description":"The project, with an `access` block.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ProjectWithAccess"}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"422":{"$ref":"#/components/responses/Unprocessable"}}},"patch":{"tags":["Projects"],"summary":"Change a project","parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"},"description":"Project id."}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"name":{"type":"string"},"description":{"type":"string"},"color":{"type":"string","enum":["blue","cyan","violet","emerald","amber","rose"],"default":"blue"},"visibility":{"type":"string","enum":["private","public"],"default":"private"},"priority":{"type":"string","enum":["low","medium","high"],"default":"medium"},"due_date":{"type":"string","format":"date","nullable":true},"archived":{"type":"boolean"}}}}}},"responses":{"200":{"description":"The updated project.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Project"}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"422":{"$ref":"#/components/responses/Unprocessable"}}},"delete":{"tags":["Projects"],"summary":"Delete a project","description":"Takes the project's whole history with it, so only the owning team may.","parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"},"description":"Project id."}],"responses":{"204":{"description":"Deleted."},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"422":{"$ref":"#/components/responses/Unprocessable"}}}},"/projects/{id}/tasks":{"get":{"tags":["Tasks"],"summary":"This project's tasks","parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"},"description":"Project id."},{"name":"kind","in":"query","schema":{"type":"string","enum":["task","post","all"],"default":"task"},"description":"Work by default. `post` returns the project's pinned board notes instead, and `all` returns both."},{"name":"status","in":"query","schema":{"type":"string","enum":["todo","doing","done"]}},{"name":"priority","in":"query","schema":{"type":"string","enum":["high","medium","low"]}},{"name":"milestone_id","in":"query","schema":{"type":"string"}},{"name":"assignee","in":"query","schema":{"type":"string"},"description":"A user id, or the literal `me`."},{"name":"due_before","in":"query","schema":{"type":"string","format":"date"},"description":"Tasks due strictly before this calendar day."},{"name":"include_archived","in":"query","schema":{"type":"boolean"}},{"name":"limit","in":"query","schema":{"type":"integer","minimum":1,"maximum":200,"default":50},"description":"How many rows to return."},{"name":"cursor","in":"query","schema":{"type":"string"},"description":"The `next_cursor` from the previous page."}],"responses":{"200":{"description":"Newest first.","content":{"application/json":{"schema":{"type":"object","properties":{"data":{"type":"array","items":{"$ref":"#/components/schemas/Task"}},"next_cursor":{"type":"string","nullable":true,"description":"Pass back as `cursor`. Null when the list is exhausted."}}}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"422":{"$ref":"#/components/responses/Unprocessable"}}},"post":{"tags":["Tasks"],"summary":"Create a task","parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"},"description":"Project id."}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["title"],"properties":{"title":{"type":"string","maxLength":200},"details":{"type":"string","maxLength":50000},"status":{"type":"string","enum":["todo","doing","done"],"default":"todo"},"priority":{"type":"string","enum":["high","medium","low"],"default":"medium"},"assignee_id":{"type":"string","nullable":true},"milestone_id":{"type":"string","nullable":true},"due_date":{"type":"string","format":"date","nullable":true},"kind":{"type":"string","enum":["task","post"],"default":"task","description":"Create only. `post` writes a note onto the project board instead of work."},"pinned":{"type":"boolean","description":"Create only. Puts the new row's card on the project board. A post always is."},"pin_color":{"type":"string","enum":["butter","peach","rose","lilac","sky","mint"],"default":"butter","description":"The paper the board card is drawn on."}}}}}},"responses":{"201":{"description":"The new task.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Task"}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"422":{"$ref":"#/components/responses/Unprocessable"}}}},"/tasks":{"get":{"tags":["Tasks"],"summary":"Tasks across every project you can reach","description":"The view an API has that the app does not really offer: 'everything assigned to me that is overdue' is one call here and a walk through the dashboard in a browser.","parameters":[{"name":"project_id","in":"query","schema":{"type":"string"}},{"name":"workspace_id","in":"query","schema":{"type":"string"}},{"name":"kind","in":"query","schema":{"type":"string","enum":["task","post","all"],"default":"task"},"description":"Work by default. `post` returns the project's pinned board notes instead, and `all` returns both."},{"name":"status","in":"query","schema":{"type":"string","enum":["todo","doing","done"]}},{"name":"priority","in":"query","schema":{"type":"string","enum":["high","medium","low"]}},{"name":"milestone_id","in":"query","schema":{"type":"string"}},{"name":"assignee","in":"query","schema":{"type":"string"},"description":"A user id, or the literal `me`."},{"name":"due_before","in":"query","schema":{"type":"string","format":"date"},"description":"Tasks due strictly before this calendar day."},{"name":"include_archived","in":"query","schema":{"type":"boolean"}},{"name":"limit","in":"query","schema":{"type":"integer","minimum":1,"maximum":200,"default":50},"description":"How many rows to return."},{"name":"cursor","in":"query","schema":{"type":"string"},"description":"The `next_cursor` from the previous page."}],"responses":{"200":{"description":"Newest first.","content":{"application/json":{"schema":{"type":"object","properties":{"data":{"type":"array","items":{"$ref":"#/components/schemas/Task"}},"next_cursor":{"type":"string","nullable":true,"description":"Pass back as `cursor`. Null when the list is exhausted."}}}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"422":{"$ref":"#/components/responses/Unprocessable"}}}},"/tasks/{id}":{"get":{"tags":["Tasks"],"summary":"One task","parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"},"description":"Task id."}],"responses":{"200":{"description":"The task.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Task"}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"422":{"$ref":"#/components/responses/Unprocessable"}}},"patch":{"tags":["Tasks"],"summary":"Change a task","description":"A field left out is left alone; a field sent as `null` is cleared. That distinction is the whole reason this is a PATCH — it lets you hand over `{\"assignee_id\": \"…\"}` without first reading the task back to avoid blanking its due date.","parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"},"description":"Task id."}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"title":{"type":"string","maxLength":200},"details":{"type":"string","maxLength":50000},"status":{"type":"string","enum":["todo","doing","done"],"default":"todo"},"priority":{"type":"string","enum":["high","medium","low"],"default":"medium"},"assignee_id":{"type":"string","nullable":true},"milestone_id":{"type":"string","nullable":true},"due_date":{"type":"string","format":"date","nullable":true},"kind":{"type":"string","enum":["task","post"],"default":"task","description":"Create only. `post` writes a note onto the project board instead of work."},"pinned":{"type":"boolean","description":"Create only. Puts the new row's card on the project board. A post always is."},"pin_color":{"type":"string","enum":["butter","peach","rose","lilac","sky","mint"],"default":"butter","description":"The paper the board card is drawn on."}}}}}},"responses":{"200":{"description":"The updated task.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Task"}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"422":{"$ref":"#/components/responses/Unprocessable"}}},"delete":{"tags":["Tasks"],"summary":"Delete a task","parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"},"description":"Task id."}],"responses":{"204":{"description":"Deleted."},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"422":{"$ref":"#/components/responses/Unprocessable"}}}},"/tasks/{id}/advance":{"post":{"tags":["Tasks"],"summary":"Move a task one step along","description":"todo → doing → done, and around again from done. What a keyboard shortcut or a chat command actually wants: advancing needs no knowledge of where the task currently is.","parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"},"description":"Task id."}],"responses":{"200":{"description":"The task in its new status.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Task"}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"422":{"$ref":"#/components/responses/Unprocessable"}}}},"/tasks/{id}/notes":{"get":{"tags":["Tasks","Notes"],"summary":"Notes linked to a task","parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"},"description":"Task id."}],"responses":{"200":{"description":"Newest connection first.","content":{"application/json":{"schema":{"type":"object","properties":{"data":{"type":"array","items":{"$ref":"#/components/schemas/LinkedNote"}},"next_cursor":{"type":"string","nullable":true,"description":"Pass back as `cursor`. Null when the list is exhausted."}}}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"422":{"$ref":"#/components/responses/Unprocessable"}}}},"/tasks/{id}/comments":{"get":{"tags":["Tasks"],"summary":"A task's thread","parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"},"description":"Task id."}],"responses":{"200":{"description":"Oldest first — the order it was read in.","content":{"application/json":{"schema":{"type":"object","properties":{"data":{"type":"array","items":{"$ref":"#/components/schemas/Comment"}},"next_cursor":{"type":"string","nullable":true,"description":"Pass back as `cursor`. Null when the list is exhausted."}}}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"422":{"$ref":"#/components/responses/Unprocessable"}}},"post":{"tags":["Tasks"],"summary":"Post to the thread","description":"An `@name` here is resolved to a person or a project agent exactly as it is from the composer in the app — so this can reach a teammate by mail, or start an agent run.","parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"},"description":"Task id."}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["body"],"properties":{"body":{"type":"string"}}}}}},"responses":{"201":{"description":"The new comment.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Comment"}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"422":{"$ref":"#/components/responses/Unprocessable"}}}},"/tasks/{id}/attachments":{"get":{"tags":["Tasks"],"summary":"A task's file attachments","parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"},"description":"Task id."}],"responses":{"200":{"description":"In the order they were added.","content":{"application/json":{"schema":{"type":"object","properties":{"data":{"type":"array","items":{"$ref":"#/components/schemas/TaskAttachment"}},"next_cursor":{"type":"string","nullable":true,"description":"Pass back as `cursor`. Null when the list is exhausted."}}}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"422":{"$ref":"#/components/responses/Unprocessable"}}},"post":{"tags":["Tasks"],"summary":"Attach files to a task","description":"Multipart only. Up to 10 files per task, 15 MB each, 60 MB total — cumulative with whatever the task already carries, not just this call's batch.","parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"},"description":"Task id."}],"requestBody":{"required":true,"content":{"multipart/form-data":{"schema":{"type":"object","required":["files"],"properties":{"files":{"type":"array","maxItems":10,"items":{"type":"string","format":"binary"}}}}}}},"responses":{"201":{"description":"The attachments as stored.","content":{"application/json":{"schema":{"type":"array","items":{"$ref":"#/components/schemas/TaskAttachment"}}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"422":{"$ref":"#/components/responses/Unprocessable"}}}},"/task-attachments/{id}":{"get":{"tags":["Tasks"],"summary":"Download a task attachment","description":"With the same bearer token used to read its task.","parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"},"description":"Attachment id."}],"responses":{"200":{"description":"The file, with its own content type.","content":{"*/*":{"schema":{"type":"string","format":"binary"}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"422":{"$ref":"#/components/responses/Unprocessable"}}},"delete":{"tags":["Tasks"],"summary":"Delete a task attachment","parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"},"description":"Attachment id."}],"responses":{"204":{"description":"Deleted."},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"422":{"$ref":"#/components/responses/Unprocessable"}}}},"/tasks/{id}/links":{"get":{"tags":["Tasks"],"summary":"A task's links","parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"},"description":"Task id."}],"responses":{"200":{"description":"In the order they were added.","content":{"application/json":{"schema":{"type":"object","properties":{"data":{"type":"array","items":{"$ref":"#/components/schemas/TaskLink"}},"next_cursor":{"type":"string","nullable":true,"description":"Pass back as `cursor`. Null when the list is exhausted."}}}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"422":{"$ref":"#/components/responses/Unprocessable"}}},"post":{"tags":["Tasks"],"summary":"Add a link to a task","description":"An Open Graph preview is fetched for it automatically, in the background.","parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"},"description":"Task id."}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["url"],"properties":{"url":{"type":"string","format":"uri"},"note":{"type":"string","maxLength":300}}}}}},"responses":{"201":{"description":"The new link.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/TaskLink"}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"422":{"$ref":"#/components/responses/Unprocessable"}}}},"/task-links/{id}":{"delete":{"tags":["Tasks"],"summary":"Remove a link from a task","parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"},"description":"Link id."}],"responses":{"204":{"description":"Deleted."},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"422":{"$ref":"#/components/responses/Unprocessable"}}}},"/notifications":{"get":{"tags":["Notifications"],"summary":"The inbox","description":"Newest first, in two tiers as on the web. `direct` is what is addressed at you: a mention, a reply, work handed to you, a direct message, a huddle. `activity` is the ambient rest: a task created or finished, a decision recorded. Each row's sentence arrives already worded in three pieces (`text.actor` in bold, the quiet `text.verb`, `text.subject` in bold), so a new kind reads correctly in an older client; `text.actor` is null for a folded row, where `count` leads. `url` is app-relative. The unread counts for both tiers come back too.","parameters":[{"name":"tier","in":"query","schema":{"type":"string","enum":["direct","activity"],"default":"direct"}},{"name":"limit","in":"query","schema":{"type":"integer","minimum":1,"maximum":100,"default":50}},{"name":"workspace_id","in":"query","schema":{"type":"string"},"description":"Defaults to the token's workspace, else the first."}],"responses":{"200":{"description":"The inbox.","content":{"application/json":{"schema":{"type":"object","properties":{"workspace_id":{"type":"string"},"tier":{"type":"string","enum":["direct","activity"]},"data":{"type":"array","items":{"type":"object","properties":{"id":{"type":"string"},"kind":{"type":"string"},"direct":{"type":"boolean"},"glyph":{"type":"string","description":"A name for the icon this kind wears."},"actor":{"type":"object","nullable":true,"properties":{"id":{"type":"string","nullable":true},"name":{"type":"string"}}},"text":{"type":"object","properties":{"actor":{"type":"string","nullable":true},"verb":{"type":"string"},"subject":{"type":"string"}}},"snippet":{"type":"string"},"count":{"type":"integer","description":"How many events a folded row stands for."},"project":{"type":"object","nullable":true,"properties":{"id":{"type":"string"},"name":{"type":"string"}}},"url":{"type":"string"},"read":{"type":"boolean"},"created_at":{"type":"string","format":"date-time"}}}},"counts":{"type":"object","properties":{"unread":{"type":"integer"},"unread_activity":{"type":"integer"}}}}}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"422":{"$ref":"#/components/responses/Unprocessable"}}}},"/notifications/counts":{"get":{"tags":["Notifications"],"summary":"Just the unread counts","description":"For a client that polls to keep a badge honest: a few counts, not a hundred rows.","parameters":[{"name":"workspace_id","in":"query","schema":{"type":"string"}}],"responses":{"200":{"description":"The counts.","content":{"application/json":{"schema":{"type":"object","properties":{"workspace_id":{"type":"string"},"unread":{"type":"integer","description":"Unread rows in the `direct` tier."},"unread_activity":{"type":"integer","description":"Unread rows in the `activity` tier."}}}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"422":{"$ref":"#/components/responses/Unprocessable"}}}},"/notifications/read-all":{"post":{"tags":["Notifications"],"summary":"Mark the inbox read","description":"One tier, or both when none is given. Changes only what you have seen.","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"tier":{"type":"string","enum":["direct","activity"]},"workspace_id":{"type":"string"}}}}}},"responses":{"200":{"description":"Done.","content":{"application/json":{"schema":{"type":"object","properties":{"ok":{"type":"boolean"}}}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"422":{"$ref":"#/components/responses/Unprocessable"}}}},"/chat-messages/{id}":{"patch":{"tags":["Chat and agents"],"summary":"Edit a message you wrote","description":"Only the author, and only a message that has not been deleted. Who it names with `@` is worked out again from the new text. A message needs text, so this cannot clear one.","parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"},"description":"Message id."}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["body"],"properties":{"body":{"type":"string","maxLength":8000}}}}}},"responses":{"200":{"description":"The edited message.","content":{"application/json":{"schema":{"type":"object","properties":{"id":{"type":"string"},"body":{"type":"string"},"edited_at":{"type":"string","format":"date-time","nullable":true}}}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"422":{"$ref":"#/components/responses/Unprocessable"}}},"delete":{"tags":["Chat and agents"],"summary":"Delete a message","description":"By the person who wrote it, or by the team that owns the project. The text, files, reactions and task links go; the message stays as a deleted one so a reply to it still has something to point at.","parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"},"description":"Message id."}],"responses":{"204":{"description":"Deleted."},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"422":{"$ref":"#/components/responses/Unprocessable"}}}},"/chat-messages/{id}/report":{"post":{"tags":["Chat and agents"],"summary":"Report a message","description":"Reports a message as objectionable. From then on the message is hidden from you, and the operator is told and reviews it within 24 hours. Reporting the same message again answers with the first report. Your own messages, and removed ones, cannot be reported.","parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"},"description":"Message id."}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["reason"],"properties":{"reason":{"type":"string","enum":["harassment","hate","sexual","violence","spam","other"]},"details":{"type":"string","maxLength":1000}}}}}},"responses":{"200":{"description":"Already reported by you.","content":{"application/json":{"schema":{"type":"object","properties":{"id":{"type":"string"},"hidden":{"type":"boolean"}}}}}},"201":{"description":"Reported.","content":{"application/json":{"schema":{"type":"object","properties":{"id":{"type":"string"},"hidden":{"type":"boolean"}}}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"422":{"$ref":"#/components/responses/Unprocessable"}}}},"/users/{id}/block":{"post":{"tags":["Chat and agents"],"summary":"Block a person","description":"Their messages, reactions and typing are hidden from you, a direct conversation with them leaves your list, nothing they do notifies you, and neither of you can message the other directly. They are not told. Blocking twice is the same as once.","parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"},"description":"User id."}],"responses":{"200":{"description":"Blocked.","content":{"application/json":{"schema":{"type":"object","properties":{"id":{"type":"string"},"name":{"type":"string"},"blocked":{"type":"boolean"}}}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"422":{"$ref":"#/components/responses/Unprocessable"}}},"delete":{"tags":["Chat and agents"],"summary":"Unblock a person","parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"},"description":"User id."}],"responses":{"204":{"description":"Unblocked, or was not blocked."},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"422":{"$ref":"#/components/responses/Unprocessable"}}}},"/conversations/{id}/mentions":{"get":{"tags":["Chat and agents"],"summary":"Who `@` can name here","description":"The project's people and its agents (by handle) in a channel — what a composer offers after an `@`. Empty in a direct chat, where `@` names no one.","parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"},"description":"Conversation id."}],"responses":{"200":{"description":"Mention targets.","content":{"application/json":{"schema":{"type":"object","properties":{"data":{"type":"array","items":{"type":"object","properties":{"id":{"type":"string"},"name":{"type":"string","description":"Exactly what follows the `@`."},"kind":{"type":"string","enum":["user","agent"]}}}}}}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"422":{"$ref":"#/components/responses/Unprocessable"}}}},"/comments/{id}":{"delete":{"tags":["Tasks"],"summary":"Delete a comment","description":"By the person who wrote it, or by the team that owns the project.","parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"},"description":"Comment id."}],"responses":{"204":{"description":"Deleted."},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"422":{"$ref":"#/components/responses/Unprocessable"}}}},"/projects/{id}/milestones":{"get":{"tags":["Milestones"],"summary":"This project's milestones","parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"},"description":"Project id."}],"responses":{"200":{"description":"Pinned first, then by date, undated last — the order the timeline reads.","content":{"application/json":{"schema":{"type":"object","properties":{"data":{"type":"array","items":{"$ref":"#/components/schemas/Milestone"}},"next_cursor":{"type":"string","nullable":true,"description":"Pass back as `cursor`. Null when the list is exhausted."}}}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"422":{"$ref":"#/components/responses/Unprocessable"}}},"post":{"tags":["Milestones"],"summary":"Create a milestone","parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"},"description":"Project id."}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["title"],"properties":{"title":{"type":"string"},"date":{"type":"string","format":"date","nullable":true},"done":{"type":"boolean"}}}}}},"responses":{"201":{"description":"The new milestone.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Milestone"}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"422":{"$ref":"#/components/responses/Unprocessable"}}}},"/milestones/{id}":{"patch":{"tags":["Milestones"],"summary":"Change a milestone","parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"},"description":"Milestone id."}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"title":{"type":"string"},"date":{"type":"string","format":"date","nullable":true},"done":{"type":"boolean"},"pinned":{"type":"boolean","description":"Pins the milestone to the top of the project's timeline, for everyone who opens it. Nothing is hidden by it — only the order changes."}}}}}},"responses":{"200":{"description":"The updated milestone.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Milestone"}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"422":{"$ref":"#/components/responses/Unprocessable"}}},"delete":{"tags":["Milestones"],"summary":"Delete a milestone","description":"Leaves the work planned against it standing, with its `milestone` set to null — losing a date should not take the tasks with it.","parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"},"description":"Milestone id."}],"responses":{"204":{"description":"Deleted."},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"422":{"$ref":"#/components/responses/Unprocessable"}}}},"/projects/{id}/decisions":{"get":{"tags":["Decisions"],"summary":"The decision log","parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"},"description":"Project id."}],"responses":{"200":{"description":"Newest first. Entries are written once, never edited.","content":{"application/json":{"schema":{"type":"object","properties":{"data":{"type":"array","items":{"$ref":"#/components/schemas/Decision"}},"next_cursor":{"type":"string","nullable":true,"description":"Pass back as `cursor`. Null when the list is exhausted."}}}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"422":{"$ref":"#/components/responses/Unprocessable"}}},"post":{"tags":["Decisions"],"summary":"Record a decision","parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"},"description":"Project id."}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["body"],"properties":{"body":{"type":"string"}}}}}},"responses":{"201":{"description":"The new entry.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Decision"}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"422":{"$ref":"#/components/responses/Unprocessable"}}}},"/decisions/{id}":{"delete":{"tags":["Decisions"],"summary":"Strike an entry from the log","description":"The owning team's to do, and never the author's alone — a log its writers can quietly tidy is not a record.","parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"},"description":"Decision id."}],"responses":{"204":{"description":"Deleted."},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"422":{"$ref":"#/components/responses/Unprocessable"}}}},"/projects/{id}/notes":{"get":{"tags":["Notes"],"summary":"This project's notes","parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"},"description":"Project id."}],"responses":{"200":{"description":"Most recently updated first.","content":{"application/json":{"schema":{"type":"object","properties":{"data":{"type":"array","items":{"$ref":"#/components/schemas/Note"}},"next_cursor":{"type":"string","nullable":true,"description":"Pass back as `cursor`. Null when the list is exhausted."}}}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"422":{"$ref":"#/components/responses/Unprocessable"}}},"post":{"tags":["Notes"],"summary":"Create a note","description":"Leave title and content out for a blank note, or provide template_id to copy defaults. Explicit title/content override template values.","parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"},"description":"Project id."}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"title":{"type":"string","maxLength":200},"content":{"type":"string","maxLength":200000,"description":"Markdown."},"template_id":{"type":"string","description":"Accessible project/workspace template to copy."}}}}}},"responses":{"201":{"description":"The new note.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Note"}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"422":{"$ref":"#/components/responses/Unprocessable"}}}},"/projects/{id}/note-templates":{"get":{"tags":["Notes"],"summary":"Templates available to a project","parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"},"description":"Project id."}],"description":"Project guests see project templates only; owning-team members also see workspace templates.","responses":{"200":{"description":"Reusable note templates.","content":{"application/json":{"schema":{"type":"object","properties":{"data":{"type":"array","items":{"$ref":"#/components/schemas/NoteTemplate"}},"next_cursor":{"type":"string","nullable":true,"description":"Pass back as `cursor`. Null when the list is exhausted."}}}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"422":{"$ref":"#/components/responses/Unprocessable"}}},"post":{"tags":["Notes"],"summary":"Create a note template","parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"},"description":"Project id."}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["name"],"properties":{"name":{"type":"string","maxLength":80},"title":{"type":"string","maxLength":200},"content":{"type":"string","maxLength":200000,"description":"Default Markdown."},"scope":{"type":"string","enum":["project","workspace"],"default":"project"}}}}}},"responses":{"201":{"description":"The new template.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/NoteTemplate"}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"422":{"$ref":"#/components/responses/Unprocessable"}}}},"/projects/{id}/note-personalization":{"get":{"tags":["Notes"],"summary":"My favorite and recently visited notes","description":"Private to the authenticated user; visits are ordered newest first and capped at 50.","parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"},"description":"Project id."}],"responses":{"200":{"description":"Personal note shortcuts.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ProjectNotePersonalization"}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"422":{"$ref":"#/components/responses/Unprocessable"}}}},"/note-templates/{id}":{"patch":{"tags":["Notes"],"summary":"Update a note template","parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"},"description":"Template id."}],"description":"Changes future instantiations only; existing notes remain independent.","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"name":{"type":"string","maxLength":80},"title":{"type":"string","maxLength":200},"content":{"type":"string","maxLength":200000,"description":"Default Markdown."}}}}}},"responses":{"200":{"description":"The updated template.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/NoteTemplate"}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"422":{"$ref":"#/components/responses/Unprocessable"}}},"delete":{"tags":["Notes"],"summary":"Delete a note template","parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"},"description":"Template id."}],"description":"Existing notes created from it remain unchanged.","responses":{"204":{"description":"Deleted."},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"422":{"$ref":"#/components/responses/Unprocessable"}}}},"/notes/{id}":{"get":{"tags":["Notes"],"summary":"One note","parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"},"description":"Note id."}],"responses":{"200":{"description":"The note.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Note"}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"422":{"$ref":"#/components/responses/Unprocessable"}}},"patch":{"tags":["Notes"],"summary":"Change a note","description":"A field left out is left alone. Writes only the note's stored copy — see the `content` field's own description on this shape.","parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"},"description":"Note id."}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"title":{"type":"string","maxLength":200},"content":{"type":"string","maxLength":200000,"description":"Markdown, replacing the note in full."}}}}}},"responses":{"200":{"description":"The updated note.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Note"}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"422":{"$ref":"#/components/responses/Unprocessable"}}},"delete":{"tags":["Notes"],"summary":"Delete a note","parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"},"description":"Note id."}],"responses":{"204":{"description":"Deleted."},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"422":{"$ref":"#/components/responses/Unprocessable"}}}},"/notes/{id}/links":{"get":{"tags":["Notes"],"summary":"A note's links and backlinks","description":"Returns resolved and unresolved `[[Note title]]` references written by this note, plus the notes that link back to it.","parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"},"description":"Note id."}],"responses":{"200":{"description":"The note graph around this note.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/NoteConnections"}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"422":{"$ref":"#/components/responses/Unprocessable"}}}},"/notes/{id}/comments":{"get":{"tags":["Notes"],"summary":"A note's anchored discussions","description":"Open threads first, with replies oldest first.","parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"},"description":"Note id."}],"responses":{"200":{"description":"Anchored comment threads.","content":{"application/json":{"schema":{"type":"object","properties":{"data":{"type":"array","items":{"$ref":"#/components/schemas/NoteComment"}},"next_cursor":{"type":"string","nullable":true,"description":"Pass back as `cursor`. Null when the list is exhausted."}}}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"422":{"$ref":"#/components/responses/Unprocessable"}}},"post":{"tags":["Notes"],"summary":"Comment on selected note text","parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"},"description":"Note id."}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["body","anchor"],"properties":{"body":{"type":"string","maxLength":4000},"anchor":{"type":"object","required":["quote","start_hint"],"properties":{"quote":{"type":"string","minLength":1,"maxLength":2000},"prefix":{"type":"string","maxLength":120},"suffix":{"type":"string","maxLength":120},"start_hint":{"type":"integer","minimum":0,"description":"Approximate character offset, used only to disambiguate repeated quotes."}}}}}}}},"responses":{"201":{"description":"The new thread.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/NoteComment"}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"422":{"$ref":"#/components/responses/Unprocessable"}}}},"/notes/{id}/metadata":{"get":{"tags":["Notes"],"summary":"A note's tags and typed properties","parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"},"description":"Note id."}],"responses":{"200":{"description":"Metadata.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/NoteMetadata"}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"422":{"$ref":"#/components/responses/Unprocessable"}}},"put":{"tags":["Notes"],"summary":"Replace a note's tags and properties","parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"},"description":"Note id."}],"description":"Replacement semantics make removals explicit. The note's Markdown is untouched.","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["tags","properties"],"properties":{"tags":{"type":"array","maxItems":20,"items":{"type":"string","maxLength":40}},"properties":{"type":"array","maxItems":30,"items":{"type":"object","required":["key","type","value"],"properties":{"key":{"type":"string","maxLength":60},"type":{"type":"string","enum":["text","number","date","checkbox","url"]},"value":{"type":"string","maxLength":500}}}}}}}}},"responses":{"200":{"description":"Updated metadata.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/NoteMetadata"}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"422":{"$ref":"#/components/responses/Unprocessable"}}}},"/notes/{id}/personalization":{"get":{"tags":["Notes"],"summary":"My state for one note","parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"},"description":"Note id."}],"responses":{"200":{"description":"Personal state.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/NotePersonalization"}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"422":{"$ref":"#/components/responses/Unprocessable"}}},"patch":{"tags":["Notes"],"summary":"Favorite or unfavorite a note","parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"},"description":"Note id."}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["favorite"],"properties":{"favorite":{"type":"boolean"}}}}}},"responses":{"200":{"description":"Updated personal state.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/NotePersonalization"}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"422":{"$ref":"#/components/responses/Unprocessable"}}}},"/notes/{id}/visit":{"post":{"tags":["Notes"],"summary":"Record that I visited a note","parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"},"description":"Note id."}],"description":"Moves the note to the front of this user's project recents without changing shared note state.","responses":{"200":{"description":"Updated personal state.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/NotePersonalization"}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"422":{"$ref":"#/components/responses/Unprocessable"}}}},"/projects/{id}/note-views":{"get":{"tags":["Notes"],"summary":"Saved smart views and their current notes","parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"},"description":"Project id."}],"responses":{"200":{"description":"Shared dynamic views.","content":{"application/json":{"schema":{"type":"object","properties":{"data":{"type":"array","items":{"$ref":"#/components/schemas/NoteSmartView"}},"next_cursor":{"type":"string","nullable":true,"description":"Pass back as `cursor`. Null when the list is exhausted."}}}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"422":{"$ref":"#/components/responses/Unprocessable"}}},"post":{"tags":["Notes"],"summary":"Save a smart view","parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"},"description":"Project id."}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["name","filter"],"properties":{"name":{"type":"string","maxLength":80},"filter":{"type":"object"}}}}}},"responses":{"201":{"description":"The new smart view.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/NoteSmartView"}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"422":{"$ref":"#/components/responses/Unprocessable"}}}},"/note-views/{id}":{"patch":{"tags":["Notes"],"summary":"Rename or change a smart view","parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"},"description":"Smart view id."}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"name":{"type":"string","maxLength":80},"filter":{"type":"object"}}}}}},"responses":{"200":{"description":"The updated smart view.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/NoteSmartView"}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"422":{"$ref":"#/components/responses/Unprocessable"}}},"delete":{"tags":["Notes"],"summary":"Delete a smart view","description":"Notes and metadata remain intact.","parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"},"description":"Smart view id."}],"responses":{"204":{"description":"Deleted."},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"422":{"$ref":"#/components/responses/Unprocessable"}}}},"/note-comments/{id}":{"patch":{"tags":["Notes"],"summary":"Edit, resolve, or reopen an anchored discussion","description":"Only the author may edit body; any project editor may resolve or reopen.","parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"},"description":"Note comment thread id."}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"body":{"type":"string","maxLength":4000},"resolved":{"type":"boolean"}}}}}},"responses":{"200":{"description":"The updated thread.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/NoteComment"}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"422":{"$ref":"#/components/responses/Unprocessable"}}},"delete":{"tags":["Notes"],"summary":"Delete an anchored discussion","description":"The author or owning team may delete it; replies are deleted with the thread.","parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"},"description":"Note comment thread id."}],"responses":{"204":{"description":"Deleted."},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"422":{"$ref":"#/components/responses/Unprocessable"}}}},"/note-comments/{id}/replies":{"post":{"tags":["Notes"],"summary":"Reply to an anchored discussion","parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"},"description":"Note comment thread id."}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["body"],"properties":{"body":{"type":"string","maxLength":4000}}}}}},"responses":{"201":{"description":"The thread with its new reply.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/NoteComment"}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"422":{"$ref":"#/components/responses/Unprocessable"}}}},"/note-comment-replies/{id}":{"patch":{"tags":["Notes"],"summary":"Edit an anchored-discussion reply","description":"Only the reply author may edit it.","parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"},"description":"Note comment reply id."}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["body"],"properties":{"body":{"type":"string","maxLength":4000}}}}}},"responses":{"200":{"description":"The updated reply.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/NoteCommentReply"}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"422":{"$ref":"#/components/responses/Unprocessable"}}},"delete":{"tags":["Notes"],"summary":"Delete an anchored-discussion reply","description":"The reply author or owning team may delete it.","parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"},"description":"Note comment reply id."}],"responses":{"204":{"description":"Deleted."},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"422":{"$ref":"#/components/responses/Unprocessable"}}}},"/notes/{id}/tasks":{"get":{"tags":["Notes","Tasks"],"summary":"Tasks linked to a note","parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"},"description":"Note id."}],"responses":{"200":{"description":"Newest connection first.","content":{"application/json":{"schema":{"type":"object","properties":{"data":{"type":"array","items":{"$ref":"#/components/schemas/LinkedTask"}},"next_cursor":{"type":"string","nullable":true,"description":"Pass back as `cursor`. Null when the list is exhausted."}}}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"422":{"$ref":"#/components/responses/Unprocessable"}}},"post":{"tags":["Notes","Tasks"],"summary":"Attach or create a linked task","description":"Send `task_id` to attach existing work idempotently. Without it, `title` creates a new task and connects it to this note.","parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"},"description":"Note id."}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"task_id":{"type":"string"},"title":{"type":"string","maxLength":200},"details":{"type":"string","maxLength":50000},"status":{"type":"string","enum":["todo","doing","done"]},"priority":{"type":"string","enum":["high","medium","low"]},"assignee_id":{"type":"string","nullable":true},"milestone_id":{"type":"string","nullable":true},"due_date":{"type":"string","format":"date","nullable":true}}}}}},"responses":{"200":{"description":"The existing linked task.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/LinkedTask"}}}},"201":{"description":"The newly created linked task.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/LinkedTask"}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"422":{"$ref":"#/components/responses/Unprocessable"}}}},"/notes/{id}/tasks/{taskId}":{"delete":{"tags":["Notes","Tasks"],"summary":"Remove a note/task connection","description":"Neither the note nor task is deleted.","parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"},"description":"Note id."},{"name":"taskId","in":"path","required":true,"schema":{"type":"string"},"description":"Task id."}],"responses":{"204":{"description":"Unlinked."},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"422":{"$ref":"#/components/responses/Unprocessable"}}}},"/projects/{id}/agents":{"get":{"tags":["Chat and agents"],"summary":"Agents you can chat with or mention","description":"The name to mention is `handle`, never `name`. `/projects/{id}/mentions` lists these alongside the people, which is the better call when the question is who you can address rather than what agents exist.","parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"},"description":"Project id."}],"responses":{"200":{"description":"In the order they were added.","content":{"application/json":{"schema":{"type":"object","properties":{"data":{"type":"array","items":{"$ref":"#/components/schemas/Agent"}},"next_cursor":{"type":"string","nullable":true,"description":"Pass back as `cursor`. Null when the list is exhausted."}}}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"422":{"$ref":"#/components/responses/Unprocessable"}}}},"/projects/{id}/mentions":{"get":{"tags":["Chat and agents"],"summary":"Everyone and everything you can @mention here","description":"The project's people and its agents in one list, each carrying `mention` — the literal text to write after the `@`.\n\nWorth calling before writing a mention rather than after wondering why one did nothing. Mentions are matched against a fixed list when the text is saved, and anything that does not match is kept as ordinary prose: nobody is notified, no agent runs, and nothing reports a problem. A person is named by their display name and an agent by its handle — two different fields, and confusing them fails silently.\n\nMentioning an agent is what triggers a run, so this is also how work is handed to one. Guests invited to this one project are included; they can be given work here like anybody else.\n\nMentions resolve in a task's details, a task comment, a note and a chat message. They do **not** resolve in a reply on a review pin, which stores its body verbatim.","parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"},"description":"Project id."}],"responses":{"200":{"description":"Who and what can be named, and the project's companion.","content":{"application/json":{"schema":{"type":"object","properties":{"data":{"type":"array","items":{"$ref":"#/components/schemas/MentionTarget"}},"pet":{"allOf":[{"$ref":"#/components/schemas/Pet"}],"nullable":true,"description":"The project's companion, or null. Not a separate target — it is one of the agents in `data`, cross-referenced by `agent_id`."}}}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"422":{"$ref":"#/components/responses/Unprocessable"}}}},"/projects/{id}/conversations":{"get":{"tags":["Chat and agents"],"summary":"This project's chats","parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"},"description":"Project id."}],"responses":{"200":{"description":"Channels, your own private agent chats, and your direct messages with people.","content":{"application/json":{"schema":{"type":"object","properties":{"data":{"type":"array","items":{"$ref":"#/components/schemas/Conversation"}},"next_cursor":{"type":"string","nullable":true,"description":"Pass back as `cursor`. Null when the list is exhausted."}}}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"422":{"$ref":"#/components/responses/Unprocessable"}}},"post":{"tags":["Chat and agents"],"summary":"Open a channel, an agent chat or a direct message","description":"`{\"kind\":\"direct\",\"agent_id\":\"…\"}` opens your private chat with one agent — sending to it starts the agent automatically. `{\"kind\":\"direct\",\"user_id\":\"…\"}` opens a direct message with another person on this project — one who can also reply, so the project's owners and editors — and returns the existing one if there is one; each message notifies the other person. Give `agent_id` or `user_id`, not both. `{\"kind\":\"channel\",\"title\":\"…\"}` creates a channel, which wakes agents only when their `@handle` appears.","parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"},"description":"Project id."}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"kind":{"type":"string","enum":["channel","direct"],"default":"channel"},"title":{"type":"string","description":"For `channel`."},"agent_id":{"type":"string","description":"For `direct`: a chat with an agent."},"user_id":{"type":"string","description":"For `direct`: a direct message with a person."}}}}}},"responses":{"201":{"description":"The conversation.","content":{"application/json":{"schema":{"type":"object","properties":{"id":{"type":"string"},"project_id":{"type":"string"},"kind":{"type":"string"}}}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"422":{"$ref":"#/components/responses/Unprocessable"}}}},"/conversations/{id}/messages":{"get":{"tags":["Chat and agents"],"summary":"Read a conversation","parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"},"description":"Conversation id."},{"name":"limit","in":"query","schema":{"type":"integer","minimum":1,"maximum":240,"default":120}},{"name":"cursor","in":"query","schema":{"type":"string"}}],"responses":{"200":{"description":"Oldest first within the page.","content":{"application/json":{"schema":{"type":"object","properties":{"data":{"type":"array","items":{"$ref":"#/components/schemas/Message"}},"next_cursor":{"type":"string","nullable":true,"description":"Pass back as `cursor`. Null when the list is exhausted."}}}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"422":{"$ref":"#/components/responses/Unprocessable"}}},"post":{"tags":["Chat and agents"],"summary":"Send a message","description":"Send JSON, or multipart form data with the same fields and up to four `files`. A direct chat wakes its agent; a channel message wakes every enabled agent named with an `@handle`.","parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"},"description":"Conversation id."}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"body":{"type":"string"},"parent_id":{"type":"string","nullable":true,"description":"Reply to a message."},"task_id":{"type":"string","nullable":true,"description":"Attach to a task."}}}},"multipart/form-data":{"schema":{"type":"object","properties":{"body":{"type":"string"},"parent_id":{"type":"string"},"task_id":{"type":"string"},"files":{"type":"array","maxItems":4,"items":{"type":"string","format":"binary"}}}}}}},"responses":{"201":{"description":"The message as posted.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Message"}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"422":{"$ref":"#/components/responses/Unprocessable"}}}},"/conversations/{id}/huddles":{"get":{"tags":["Chat and agents"],"summary":"The call running in a channel, if any","parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"},"description":"Conversation id. Must be a project channel."}],"responses":{"200":{"description":"The active huddle, or none.","content":{"application/json":{"schema":{"type":"object","properties":{"huddle":{"$ref":"#/components/schemas/Huddle","nullable":true}}}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"422":{"$ref":"#/components/responses/Unprocessable"}}},"post":{"tags":["Chat and agents"],"summary":"Start a huddle, or join the one already running","description":"Starting requires write access to the channel, same as posting. Once one is running, anyone who can read the channel may join it — this endpoint answers either way.","parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"},"description":"Conversation id. Must be a project channel."}],"responses":{"201":{"description":"Connection details for the huddle.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HuddleConnection"}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"422":{"$ref":"#/components/responses/Unprocessable"}}}},"/huddles/{id}/join":{"post":{"tags":["Chat and agents"],"summary":"Join a huddle by id","parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"},"description":"Huddle id."}],"responses":{"200":{"description":"Connection details for the huddle.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HuddleConnection"}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"422":{"$ref":"#/components/responses/Unprocessable"}}}},"/huddles/{id}/leave":{"post":{"tags":["Chat and agents"],"summary":"Leave a huddle","description":"The huddle itself ends once nobody is left in it.","parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"},"description":"Huddle id."}],"responses":{"200":{"description":"Left.","content":{"application/json":{"schema":{"type":"object","properties":{"ok":{"type":"boolean"}}}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"422":{"$ref":"#/components/responses/Unprocessable"}}}},"/huddles/{id}/end":{"post":{"tags":["Chat and agents"],"summary":"End a huddle for everyone","description":"Kept to whoever started it, or the workspace that owns the project.","parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"},"description":"Huddle id."}],"responses":{"200":{"description":"Ended.","content":{"application/json":{"schema":{"type":"object","properties":{"ok":{"type":"boolean"}}}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"422":{"$ref":"#/components/responses/Unprocessable"}}}},"/projects/{id}/previews":{"get":{"tags":["Previews"],"summary":"This project's previews","parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"},"description":"Project id."}],"responses":{"200":{"description":"Newest first.","content":{"application/json":{"schema":{"type":"object","properties":{"data":{"type":"array","items":{"$ref":"#/components/schemas/Preview"}},"next_cursor":{"type":"string","nullable":true,"description":"Pass back as `cursor`. Null when the list is exhausted."}}}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"422":{"$ref":"#/components/responses/Unprocessable"}}},"post":{"tags":["Previews"],"summary":"Register a build for review","description":"The review link exists from this moment; nothing reaches it until the snippet is on the page. `GET /previews/{id}/review-link` returns both.","parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"},"description":"Project id."}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["url"],"properties":{"name":{"type":"string","maxLength":120,"description":"Defaults to the URL's hostname."},"url":{"type":"string","format":"uri","description":"Full URL, including https://."},"extra_origins":{"oneOf":[{"type":"array","items":{"type":"string"}},{"type":"string"}],"description":"Other origins the same build is served from."}}}}}},"responses":{"201":{"description":"The new preview.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Preview"}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"422":{"$ref":"#/components/responses/Unprocessable"}}}},"/previews/{id}":{"get":{"tags":["Previews"],"summary":"One preview","parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"},"description":"Preview id."}],"responses":{"200":{"description":"The preview.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Preview"}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"422":{"$ref":"#/components/responses/Unprocessable"}}},"patch":{"tags":["Previews"],"summary":"Change a preview","description":"A field left out is left alone. `status: \"closed\"` ends the review: the threads stay, the overlay stops being served, and every reviewer key stops resolving.","parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"},"description":"Preview id."}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"name":{"type":"string","maxLength":120},"url":{"type":"string","format":"uri"},"extra_origins":{"oneOf":[{"type":"array","items":{"type":"string"}},{"type":"string"}]},"status":{"type":"string","enum":["open","closed"]}}}}}},"responses":{"200":{"description":"The updated preview.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Preview"}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"422":{"$ref":"#/components/responses/Unprocessable"}}},"delete":{"tags":["Previews"],"summary":"Delete a preview","description":"Takes every pin on it too. Closing it instead keeps what was said.","parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"},"description":"Preview id."}],"responses":{"204":{"description":"Deleted."},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"422":{"$ref":"#/components/responses/Unprocessable"}}}},"/previews/{id}/review-link":{"get":{"tags":["Previews"],"summary":"The review link, and the snippet it needs","description":"Behind an edit grant rather than on every list: the link is a credential — whoever holds it may comment — for the same reason a project's share token is never returned with the project.\n\nThis is also the only *followable* address a preview has, and the one to use for anything meaning “open the preview”. `hosted_url` is where the files answer, on an origin that receives no coLab session, so a request there is refused without a ticket — and a build opened directly carries no review overlay, so the notes left on it are invisible. This link mints the ticket and switches the overlay on.","parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"},"description":"Preview id."},{"name":"at","in":"query","schema":{"type":"string"},"description":"A path on the build to open, such as `/pricing`, so a note left deep in a flow opens on the page it was left on. Defaults to the build's front door."}],"responses":{"200":{"description":"The link and the tag.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ReviewLink"}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"422":{"$ref":"#/components/responses/Unprocessable"}}},"post":{"tags":["Previews"],"summary":"Rotate the review link","description":"Mints a new link and kills the old one, along with every reviewer key issued against it. What people already wrote stays where it is.","parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"},"description":"Preview id."}],"responses":{"200":{"description":"The new link.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ReviewLink"}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"422":{"$ref":"#/components/responses/Unprocessable"}}}},"/previews/{id}/threads":{"get":{"tags":["Previews"],"summary":"The pins on a preview","description":"Newest first, each with its whole conversation.","parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"},"description":"Preview id."},{"name":"status","in":"query","schema":{"type":"string","enum":["open","resolved"]},"description":"`open` is the list of what this build still owes somebody."},{"name":"limit","in":"query","schema":{"type":"integer","minimum":1,"maximum":200,"default":50}}],"responses":{"200":{"description":"Newest pin first.","content":{"application/json":{"schema":{"type":"object","properties":{"data":{"type":"array","items":{"$ref":"#/components/schemas/PreviewThread"}},"next_cursor":{"type":"string","nullable":true,"description":"Pass back as `cursor`. Null when the list is exhausted."}}}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"422":{"$ref":"#/components/responses/Unprocessable"}}}},"/preview-threads/{id}":{"get":{"tags":["Previews"],"summary":"One pin and its conversation","parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"},"description":"Thread id."}],"responses":{"200":{"description":"The thread.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/PreviewThread"}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"422":{"$ref":"#/components/responses/Unprocessable"}}},"patch":{"tags":["Previews"],"summary":"Resolve a pin, or put it back","parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"},"description":"Thread id."}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["status"],"properties":{"status":{"type":"string","enum":["open","resolved"]}}}}}},"responses":{"200":{"description":"The updated thread.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/PreviewThread"}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"422":{"$ref":"#/components/responses/Unprocessable"}}},"delete":{"tags":["Previews"],"summary":"Take a pin down","parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"},"description":"Thread id."}],"responses":{"204":{"description":"Deleted."},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"422":{"$ref":"#/components/responses/Unprocessable"}}}},"/preview-threads/{id}/comments":{"get":{"tags":["Previews"],"summary":"One pin's conversation","parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"},"description":"Thread id."}],"responses":{"200":{"description":"Oldest first — the order it was said in.","content":{"application/json":{"schema":{"type":"object","properties":{"data":{"type":"array","items":{"$ref":"#/components/schemas/PreviewComment"}},"next_cursor":{"type":"string","nullable":true,"description":"Pass back as `cursor`. Null when the list is exhausted."}}}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"422":{"$ref":"#/components/responses/Unprocessable"}}},"post":{"tags":["Previews"],"summary":"Answer a reviewer where they asked","description":"The reply appears in the overlay on the build itself, not only in coLab, so the person who pinned it is told without an account and without being emailed.","parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"},"description":"Thread id."}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["body"],"properties":{"body":{"type":"string","maxLength":4000}}}}}},"responses":{"201":{"description":"The reply.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/PreviewComment"}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"422":{"$ref":"#/components/responses/Unprocessable"}}}},"/preview-threads/{id}/task":{"post":{"tags":["Previews"],"summary":"Turn a pin into a task","description":"The task quotes the thread rather than only linking to it — a preview URL is the thing here most likely to stop resolving. A pin that is already a task is refused.","parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"},"description":"Thread id."}],"responses":{"201":{"description":"The new task.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Task"}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"422":{"$ref":"#/components/responses/Unprocessable"}}}},"/preview-threads/{id}/screenshot":{"get":{"tags":["Previews"],"summary":"What the reviewer saw","description":"The viewport as their own browser captured it. 404 when there is none.","parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"},"description":"Thread id."}],"responses":{"200":{"description":"A PNG or JPEG.","content":{"image/*":{"schema":{"type":"string","format":"binary"}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"422":{"$ref":"#/components/responses/Unprocessable"}}}},"/link-images/{id}":{"get":{"tags":["Chat and agents"],"summary":"The picture on a link preview","description":"The `image_url` of a message's `previews` entry, with the same bearer token used to read its conversation.","parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"},"description":"Link preview id."}],"responses":{"200":{"description":"An image.","content":{"image/*":{"schema":{"type":"string","format":"binary"}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"422":{"$ref":"#/components/responses/Unprocessable"}}}},"/chat-attachments/{id}":{"get":{"tags":["Chat and agents"],"summary":"Download an attachment","description":"With the same bearer token used to read its conversation.","parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"},"description":"Attachment id."}],"responses":{"200":{"description":"The file, with its own content type.","content":{"*/*":{"schema":{"type":"string","format":"binary"}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"422":{"$ref":"#/components/responses/Unprocessable"}}}}},"components":{"securitySchemes":{"bearerAuth":{"type":"http","scheme":"bearer","description":"An API token: `Authorization: Bearer colab_pat_…`"}},"schemas":{"Error":{"type":"object","description":"Every failure, without exception, has this shape.","required":["error"],"properties":{"error":{"type":"object","required":["code","message"],"properties":{"code":{"type":"string","description":"Machine-readable. Switch on this, not on the message.","examples":["invalid_token","insufficient_scope","not_found","invalid_request"]},"message":{"type":"string","description":"Human-readable. Wording is not contractual."}}}}},"Person":{"type":"object","nullable":true,"properties":{"id":{"type":"string"},"name":{"type":"string"},"email":{"type":"string","format":"email"}}},"Workspace":{"type":"object","properties":{"id":{"type":"string"},"name":{"type":"string"},"role":{"type":"string","description":"This person's role in the workspace."},"created_at":{"type":"string","format":"date-time","nullable":true}}},"Project":{"type":"object","properties":{"id":{"type":"string"},"name":{"type":"string"},"description":{"type":"string"},"color":{"type":"string","enum":["blue","cyan","violet","emerald","amber","rose"],"description":"One of six fixed accents. A name, never a hex."},"visibility":{"type":"string","enum":["private","public"]},"priority":{"type":"string","enum":["low","medium","high"]},"due_date":{"type":"string","format":"date","nullable":true},"archived":{"type":"boolean"},"created_at":{"type":"string","format":"date-time","nullable":true},"workspace":{"type":"object","properties":{"id":{"type":"string"},"name":{"type":"string"}},"description":"`name` is present when the workspace was loaded with the project."},"counts":{"type":"object","properties":{"tasks":{"type":"integer"},"milestones":{"type":"integer"},"decisions":{"type":"integer"},"notes":{"type":"integer"}}}}},"ProjectWithAccess":{"allOf":[{"$ref":"#/components/schemas/Project"},{"type":"object","properties":{"access":{"type":"object","description":"What the caller may do with this project, so a client can disable controls rather than discover the rules by collecting a 403.","properties":{"can_edit":{"type":"boolean"},"is_team":{"type":"boolean"},"guest_role":{"type":"string","nullable":true}}}}}]},"Task":{"type":"object","properties":{"id":{"type":"string"},"title":{"type":"string"},"details":{"type":"string"},"mentions":{"type":"array","items":{"type":"string"},"description":"Ids resolved when details were last saved, so mentions stay queryable."},"kind":{"type":"string","enum":["task","post"],"description":"`post` is a note pinned to the project's board — standing information rather than work. It carries the same thread, mentions, attachments and links as a task, but `status`, `priority`, `due_date` and `assignee` mean nothing on one and no count in the product reads them."},"pinned_at":{"type":"string","format":"date-time","nullable":true,"description":"When this was pinned to the project's board, null when it is not on it. A post is always pinned; a task may be pinned too, and its card then points back at it."},"status":{"type":"string","enum":["todo","doing","done"]},"priority":{"type":"string","enum":["high","medium","low"]},"due_date":{"type":"string","format":"date","nullable":true,"description":"A calendar day, not an instant. Stored at UTC midnight."},"created_at":{"type":"string","format":"date-time","nullable":true},"project":{"type":"object","properties":{"id":{"type":"string"},"name":{"type":"string"},"workspace_id":{"type":"string"}},"description":"Only `id` is present when the task was loaded without its project."},"assignee":{"$ref":"#/components/schemas/Person"},"milestone":{"type":"object","nullable":true,"properties":{"id":{"type":"string"},"title":{"type":"string"},"date":{"type":"string","format":"date","nullable":true}}},"comment_count":{"type":"integer"},"attachment_count":{"type":"integer"},"link_count":{"type":"integer"}}},"TaskAttachment":{"type":"object","properties":{"id":{"type":"string"},"file_name":{"type":"string"},"mime_type":{"type":"string"},"size":{"type":"integer"},"url":{"type":"string","description":"Fetch with the same bearer token used to read the task."},"created_at":{"type":"string","format":"date-time","nullable":true}}},"TaskLink":{"type":"object","properties":{"id":{"type":"string"},"url":{"type":"string"},"note":{"type":"string"},"title":{"type":"string","description":"From the link's Open Graph preview, when fetched."},"description":{"type":"string"},"site_name":{"type":"string"},"image_url":{"type":"string","nullable":true},"created_at":{"type":"string","format":"date-time","nullable":true}}},"Milestone":{"type":"object","properties":{"id":{"type":"string"},"project_id":{"type":"string"},"title":{"type":"string"},"date":{"type":"string","format":"date","nullable":true},"done":{"type":"boolean"},"pinned":{"type":"boolean"},"pinned_at":{"type":"string","format":"date-time","nullable":true},"created_at":{"type":"string","format":"date-time","nullable":true},"task_count":{"type":"integer"}}},"Comment":{"type":"object","properties":{"id":{"type":"string"},"task_id":{"type":"string"},"body":{"type":"string"},"mentions":{"type":"array","items":{"type":"string"},"description":"Ids resolved when the text was written, so mentions stay queryable."},"author":{"type":"object","properties":{"id":{"type":"string","nullable":true,"description":"Null when an agent wrote it."},"name":{"type":"string"}}},"created_at":{"type":"string","format":"date-time","nullable":true}}},"Decision":{"type":"object","properties":{"id":{"type":"string"},"project_id":{"type":"string"},"body":{"type":"string"},"author":{"type":"object","properties":{"id":{"type":"string","nullable":true},"name":{"type":"string"}}},"created_at":{"type":"string","format":"date-time","nullable":true}}},"Note":{"type":"object","properties":{"id":{"type":"string"},"project_id":{"type":"string"},"title":{"type":"string"},"content":{"type":"string","description":"Markdown. This is the note's durable stored copy — while someone has the note open in coLab's own collaborative editor, their live document can differ from this until they save."},"created_by":{"type":"string","nullable":true},"created_at":{"type":"string","format":"date-time","nullable":true},"updated_at":{"type":"string","format":"date-time","nullable":true},"attachment_count":{"type":"integer"}}},"NoteTemplate":{"type":"object","description":"Reusable Markdown copied into an independent note when applied.","properties":{"id":{"type":"string"},"workspace_id":{"type":"string"},"project_id":{"type":"string","nullable":true},"scope":{"type":"string","enum":["project","workspace"]},"name":{"type":"string"},"title":{"type":"string"},"content":{"type":"string","description":"Default note body as Markdown."},"created_at":{"type":"string","format":"date-time","nullable":true},"updated_at":{"type":"string","format":"date-time","nullable":true}}},"NotePersonalization":{"type":"object","description":"Private state belonging only to the authenticated user.","properties":{"favorite":{"type":"boolean"},"last_visited_at":{"type":"string","format":"date-time","nullable":true}}},"ProjectNotePersonalization":{"type":"object","description":"The authenticated user's favorite and recently visited notes in one project.","properties":{"favorites":{"type":"array","items":{"type":"object","properties":{"id":{"type":"string"},"title":{"type":"string"},"updated_at":{"type":"string","format":"date-time","nullable":true}}}},"recents":{"type":"array","maxItems":50,"items":{"type":"object","properties":{"id":{"type":"string"},"title":{"type":"string"},"updated_at":{"type":"string","format":"date-time","nullable":true},"last_visited_at":{"type":"string","format":"date-time"}}}}}},"NoteConnections":{"type":"object","properties":{"outgoing":{"type":"array","items":{"type":"object","properties":{"title":{"type":"string"},"alias":{"type":"string","nullable":true},"resolved":{"type":"boolean"},"target":{"type":"object","nullable":true,"properties":{"id":{"type":"string"},"title":{"type":"string"}}}}}},"backlinks":{"type":"array","items":{"type":"object","properties":{"id":{"type":"string"},"title":{"type":"string"}}}}}},"NoteCommentReply":{"type":"object","properties":{"id":{"type":"string"},"thread_id":{"type":"string"},"body":{"type":"string"},"author":{"type":"object","properties":{"id":{"type":"string","nullable":true},"agent_id":{"type":"string","nullable":true},"name":{"type":"string"}}},"created_at":{"type":"string","format":"date-time","nullable":true},"edited_at":{"type":"string","format":"date-time","nullable":true}}},"NoteComment":{"type":"object","description":"A discussion anchored to selected note text. If later edits remove the quote, the thread remains readable and clients should present its anchor as orphaned.","properties":{"id":{"type":"string"},"note_id":{"type":"string"},"body":{"type":"string"},"anchor":{"type":"object","properties":{"quote":{"type":"string"},"prefix":{"type":"string"},"suffix":{"type":"string"},"start_hint":{"type":"integer","minimum":0}}},"author":{"type":"object","properties":{"id":{"type":"string","nullable":true},"agent_id":{"type":"string","nullable":true},"name":{"type":"string"}}},"status":{"type":"string","enum":["open","resolved"]},"resolved_at":{"type":"string","format":"date-time","nullable":true},"resolved_by_name":{"type":"string","nullable":true},"created_at":{"type":"string","format":"date-time","nullable":true},"edited_at":{"type":"string","format":"date-time","nullable":true},"replies":{"type":"array","items":{"$ref":"#/components/schemas/NoteCommentReply"}}}},"NoteMetadata":{"type":"object","properties":{"tags":{"type":"array","items":{"type":"object","properties":{"id":{"type":"string"},"name":{"type":"string"}}}},"properties":{"type":"array","items":{"type":"object","properties":{"id":{"type":"string"},"key":{"type":"string"},"type":{"type":"string","enum":["text","number","date","checkbox","url"]},"value":{"type":"string"}}}}}},"NoteSmartView":{"type":"object","description":"A shared dynamic note collection; membership is evaluated from current metadata.","properties":{"id":{"type":"string"},"project_id":{"type":"string"},"name":{"type":"string"},"filter":{"type":"object","properties":{"version":{"type":"integer","enum":[1]},"match":{"type":"string","enum":["all","any"]},"tags":{"type":"array","items":{"type":"string"}},"properties":{"type":"array","items":{"type":"object","properties":{"key":{"type":"string"},"operator":{"type":"string","enum":["equals","contains","exists"]},"value":{"type":"string"}}}}}},"notes":{"type":"array","items":{"type":"object","properties":{"id":{"type":"string"},"title":{"type":"string"},"updated_at":{"type":"string","format":"date-time"}}}}}},"LinkedTask":{"type":"object","description":"A project task connected to a note for context.","properties":{"id":{"type":"string"},"project_id":{"type":"string"},"title":{"type":"string"},"status":{"type":"string","enum":["todo","doing","done"]},"priority":{"type":"string","enum":["high","medium","low"]},"due_date":{"type":"string","format":"date","nullable":true},"assignee":{"type":"object","nullable":true,"properties":{"id":{"type":"string"},"name":{"type":"string"}}},"linked_at":{"type":"string","format":"date-time","nullable":true}}},"LinkedNote":{"type":"object","description":"A project note connected to a task for context.","properties":{"id":{"type":"string"},"project_id":{"type":"string"},"title":{"type":"string"},"updated_at":{"type":"string","format":"date-time","nullable":true},"linked_at":{"type":"string","format":"date-time","nullable":true}}},"Preview":{"type":"object","description":"A build that is still changing, and the review link people comment on it through. The token in that link is deliberately absent here — see `/previews/{id}/review-link`.","properties":{"id":{"type":"string"},"project_id":{"type":"string"},"name":{"type":"string","description":"What this build is called. `Staging`, `PR 42`."},"url":{"type":"string","format":"uri","description":"Where the build is."},"extra_origins":{"type":"array","items":{"type":"string","format":"uri"},"description":"Other origins the same build is served from, which the overlay is allowed to call coLab from. The preview URL's own origin is always allowed and need not be listed."},"status":{"type":"string","enum":["open","closed"],"description":"A closed review keeps everything that was said and stops serving the overlay, which is how a review ends without deleting it."},"source":{"type":"string","enum":["url","bundle"],"description":"`url` for a build hosted elsewhere, `bundle` for one uploaded to coLab. `url` is empty for the second."},"hosted_url":{"type":"string","format":"uri","nullable":true,"description":"Where an uploaded bundle's files answer, and **not a link to follow**. It is served from its own origin, which no coLab cookie reaches, so a request there carries no session and is refused unless it holds a ticket — and a build opened directly has no review overlay on it, which is where the pinned notes are. Tickets are minted by the review link. Use `/previews/{id}/review-link` for an address that opens."},"app_url":{"type":"string","format":"uri","description":"The preview inside coLab, for somebody who is already signed in. Not a credential, which is why it can ride along here while the review link cannot."},"access":{"type":"string","enum":["link","members","people"],"description":"Who may open the review link: anyone holding it, project members only, or named guests only."},"upload":{"type":"object","nullable":true,"description":"The uploaded bundle behind a `bundle` preview, or null.","properties":{"version":{"type":"integer"},"file_name":{"type":"string"},"entry_path":{"type":"string"},"file_count":{"type":"integer"},"bytes":{"type":"integer"},"updated_at":{"type":"string","format":"date-time","nullable":true}}},"thread_count":{"type":"integer"},"open_thread_count":{"type":"integer"},"created_at":{"type":"string","format":"date-time","nullable":true},"updated_at":{"type":"string","format":"date-time","nullable":true}}},"PreviewComment":{"type":"object","properties":{"id":{"type":"string"},"thread_id":{"type":"string"},"body":{"type":"string"},"author":{"type":"object","properties":{"side":{"type":"string","enum":["reviewer","team"],"description":"Which side of the review said it: someone on the build's own page holding the review link, or someone answering from inside coLab."},"name":{"type":"string","description":"Copied on as it was written, so it outlives the account."},"reviewer_id":{"type":"string","nullable":true},"user_id":{"type":"string","nullable":true},"agent_id":{"type":"string","nullable":true}}},"created_at":{"type":"string","format":"date-time","nullable":true}}},"PreviewThread":{"type":"object","description":"One pin on one element of one page, and the conversation hanging off it.","properties":{"id":{"type":"string"},"preview_id":{"type":"string"},"number":{"type":"integer","description":"Per-preview, so a reviewer can say “#4”."},"page":{"type":"string","description":"Path and query of the page the pin is on, to be resolved against the preview's URL."},"status":{"type":"string","enum":["open","resolved"]},"author":{"type":"object","properties":{"name":{"type":"string"},"reviewer_id":{"type":"string","nullable":true}}},"anchor":{"type":"object","description":"How the overlay finds the element again, in the order it tries: the selector, then the fraction of the way across and down the element's own box, then the text that was in it when the pin was dropped.","properties":{"selector":{"type":"string"},"text":{"type":"string"},"offset_x":{"type":"number"},"offset_y":{"type":"number"}}},"suggestion":{"type":"object","nullable":true,"description":"Set when the pin proposes different words rather than remarking on them.","properties":{"before":{"type":"string"},"after":{"type":"string"}}},"seen_at":{"type":"object","description":"The conditions the pin was dropped under. A bug at 390px needs this.","properties":{"width":{"type":"integer"},"height":{"type":"integer"},"user_agent":{"type":"string"}}},"screenshot":{"type":"object","nullable":true,"description":"Null when the reviewer's browser could not take one.","properties":{"size":{"type":"integer"},"url":{"type":"string"}}},"task":{"type":"object","nullable":true,"description":"The task this pin became, if somebody made one. `title` is null on a list.","properties":{"id":{"type":"string"},"title":{"type":"string","nullable":true},"status":{"type":"string","nullable":true}}},"comments":{"type":"array","items":{"$ref":"#/components/schemas/PreviewComment"}},"resolved_at":{"type":"string","format":"date-time","nullable":true},"created_at":{"type":"string","format":"date-time","nullable":true}}},"ReviewLink":{"type":"object","properties":{"preview_id":{"type":"string"},"review_url":{"type":"string","format":"uri","description":"Send this to whoever should comment. Anyone holding it can, with no account: they are asked for a name, then sent to the build with the overlay switched on."},"at":{"type":"string","nullable":true,"description":"The path `review_url` opens on, when one was asked for. Null means the front door."},"snippet":{"type":"string","description":"The tag that has to be on the build, once, anywhere in the page. Without it the review link opens the site and nothing happens."},"status":{"type":"string","enum":["open","closed"]}}},"Agent":{"type":"object","properties":{"id":{"type":"string"},"name":{"type":"string","description":"What it is called. Not what it answers to."},"handle":{"type":"string","description":"Mentioned as `@handle`, without the `@`. This — not `name` — is what a mention must match; an agent named by its `name` silently does not resolve."},"model":{"type":"string"},"enabled":{"type":"boolean","description":"A disabled agent still matches a mention. It just never answers."},"is_pet":{"type":"boolean","description":"Whether this is the agent the project dresses up as its companion."}}},"Pet":{"type":"object","description":"A project's companion: one of its agents in a costume, not a thing of its own. `handle` is that agent's handle, and mentioning it is mentioning the agent.","properties":{"agent_id":{"type":"string","description":"The agent this is. Cross-references `Agent.id`."},"display_name":{"type":"string","description":"The name to say, which follows the agent even after it is renamed."},"handle":{"type":"string"},"sprite_id":{"type":"string"},"spritesheet_path":{"type":"string"},"personality":{"type":"string"},"speech_style":{"type":"string"},"ambient_enabled":{"type":"boolean"},"ambient_frequency":{"type":"string","enum":["low","normal","high"]}}},"MentionTarget":{"type":"object","description":"Something an `@` can name in a project — a person or an agent.","properties":{"id":{"type":"string"},"kind":{"type":"string","enum":["user","agent"]},"mention":{"type":"string","description":"The literal text to write after `@`. A person's display name, or an agent's handle. Use this rather than assembling it from the fields below."},"name":{"type":"string"},"email":{"type":"string","format":"email","description":"People only."},"handle":{"type":"string","description":"Agents only."},"model":{"type":"string","description":"Agents only."},"enabled":{"type":"boolean","description":"Agents only."},"is_pet":{"type":"boolean","description":"Agents only."}}},"Conversation":{"type":"object","properties":{"id":{"type":"string"},"project_id":{"type":"string"},"kind":{"type":"string","enum":["channel","direct"]},"title":{"type":"string"},"slug":{"type":"string","nullable":true},"archived":{"type":"boolean"},"agent":{"allOf":[{"$ref":"#/components/schemas/Agent"}],"nullable":true,"description":"The agent on the other side of a `direct` chat; null for a channel."},"direct_with":{"type":"string","enum":["person","agent"],"nullable":true,"description":"Who a `direct` conversation is with. A chat with an agent stays `agent` after the agent is deleted. Null for a channel."},"person":{"type":"object","nullable":true,"properties":{"id":{"type":"string"},"name":{"type":"string"}},"description":"The other person in a `direct` message between two people, as the caller sees it — `title` is already their name. Null for a channel and for a chat with an agent."},"last_message":{"type":"object","nullable":true},"created_at":{"type":"string","format":"date-time","nullable":true},"updated_at":{"type":"string","format":"date-time","nullable":true}}},"Message":{"type":"object","properties":{"id":{"type":"string"},"conversation_id":{"type":"string"},"parent":{"type":"object","nullable":true,"description":"The message this replies to."},"body":{"type":"string"},"mentions":{"type":"array","items":{"type":"string"}},"author":{"type":"object","properties":{"id":{"type":"string","nullable":true},"agent_id":{"type":"string","nullable":true},"name":{"type":"string"}}},"attachments":{"type":"array","items":{"type":"object","properties":{"id":{"type":"string"},"file_name":{"type":"string"},"mime_type":{"type":"string"},"size":{"type":"integer"},"url":{"type":"string","description":"Fetch with the same bearer token used to read the conversation."}}}},"agent_run":{"type":"object","nullable":true,"description":"Present when this message started or came from an agent run."},"previews":{"type":"array","description":"Cards for the links in `body`, in the order they appear. Worked out from the text on every read, so an edited message brings its own along. A link that has not been fetched yet, or could not be, has no card.","items":{"type":"object","properties":{"url":{"type":"string"},"title":{"type":"string"},"description":{"type":"string"},"site_name":{"type":"string"},"host":{"type":"string"},"image_url":{"type":"string","nullable":true,"description":"Fetch with the same bearer token used to read the conversation."}}}},"agent_reactions":{"type":"array","description":"Reactions an agent left instead of running — an acknowledgement such as \"ok\" or \"thanks\" that needs no answer. Separate from `reactions`: a person cannot toggle these, and the emoji is not limited to the ones people can use.","items":{"type":"object","properties":{"emoji":{"type":"string"},"name":{"type":"string","description":"The agent's name."}}}},"edited_at":{"type":"string","format":"date-time","nullable":true},"deleted_at":{"type":"string","format":"date-time","nullable":true},"created_at":{"type":"string","format":"date-time","nullable":true}}},"Huddle":{"type":"object","description":"A call currently running in a project channel.","properties":{"id":{"type":"string"},"started_by_name":{"type":"string"},"started_at":{"type":"string","format":"date-time"},"participants":{"type":"array","description":"Everyone still connected, not everyone who has ever joined.","items":{"type":"object","properties":{"id":{"type":"string"},"name":{"type":"string"}}}}}},"HuddleConnection":{"type":"object","description":"What a LiveKit client (livekit-client, or any of its server SDKs) needs to open the WebRTC connection. `token` is a bearer credential good for this one room only.","properties":{"huddle_id":{"type":"string"},"conversation_id":{"type":"string"},"project_id":{"type":"string"},"room_name":{"type":"string"},"livekit_url":{"type":"string","description":"A wss:// URL."},"token":{"type":"string"},"can_end":{"type":"boolean","description":"Whether this caller may end the huddle for everyone."}}},"Me":{"type":"object","properties":{"user":{"type":"object","properties":{"id":{"type":"string"},"name":{"type":"string"}}},"token":{"type":"object","properties":{"id":{"type":"string"},"scope":{"type":"string","enum":["read","write"]},"workspace_id":{"type":"string","nullable":true,"description":"Set only when the token was pinned to one workspace."}}},"workspaces":{"type":"array","items":{"$ref":"#/components/schemas/Workspace"}}}},"SearchHit":{"type":"object","properties":{"id":{"type":"string","description":"The id of the matched row, in its own resource."},"kind":{"type":"string","enum":["project","task","note","milestone","decision","comment","message","attachment","link","preview","pin"]},"title":{"type":"string"},"snippet":{"type":"string","nullable":true,"description":"The matched text, trimmed to a window around the match."},"note":{"type":"string","nullable":true,"description":"A short qualifier, such as a status."},"project":{"type":"object","description":"The project the hit belongs to. Everything searchable belongs to one.","properties":{"id":{"type":"string"},"name":{"type":"string"}}},"href":{"type":"string","description":"Where this opens in the app."}}},"SearchResult":{"type":"object","description":"Ranked hits, most relevant first, already flattened out of their groups.","properties":{"query":{"type":"string","description":"Exactly what was asked for."},"searched_for":{"type":"string","description":"What was actually searched for — the correction, when one was applied."},"corrected_from":{"type":"string","nullable":true,"description":"Set when a spelling correction was applied, so you can offer the way back."},"mentioned":{"type":"object","nullable":true,"description":"The person a leading `@Name` resolved to.","properties":{"id":{"type":"string"},"name":{"type":"string"}}},"unresolved_mention":{"type":"string","nullable":true,"description":"An `@Name` that matched nobody. The search did not run — say so rather than reporting no results."},"total":{"type":"integer"},"data":{"type":"array","items":{"$ref":"#/components/schemas/SearchHit"}}}}},"responses":{"Unauthorized":{"description":"Missing or invalid token.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"Forbidden":{"description":"The token's scope is too narrow.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"NotFound":{"description":"No such row, or not one you can reach.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"Unprocessable":{"description":"The body or a query parameter did not validate.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}}