{"openapi":"3.1.0","info":{"title":"Tasg API","version":"v1","description":"REST API for Tasg: the same endpoints the web app uses, under a versioned prefix.\n\n**Authentication.** Send `Authorization: Bearer <token>`. Two kinds of token work everywhere:\n\n- **Personal API token** (`tf_…`): long-lived, scoped (`read`, `write`, `delete`). Create one in Developers → API tokens or with `POST /tokens`. For integrations that shouldn't act as a person, mint a **service account** (see that section): same token mechanics, its own actor identity. Each operation's required scope is shown as **x-token-scope**; a token missing it gets **403**.\n- **Session JWT**: what the web app uses; 15-minute lifetime, refresh via `POST /auth/refresh`.\n\n**Tenancy.** Everything is scoped to the workspaces and projects you belong to. Resources you cannot see return **404**, not 403, so existence is never leaked.\n\n**Conventions.** JSON bodies, snake_case fields (a few older objects use camelCase and say so), ISO-8601 timestamps, `YYYY-MM-DD` calendar dates. Errors are `{ \"error\": \"…\" }`; validation failures add `details[]`; duplicates are **409**, broken references **400**, bodies over 10 MB are **413**. Client mistakes never surface as 500s. List endpoints that paginate return `total`, `page`, `per_page`.\n\n**Idempotency.** Send an `Idempotency-Key` header (any unique string, ≤200 chars) on a POST and a retry of the same request within 24h replays the original response (marked `Idempotency-Replayed: true`) instead of creating a duplicate. Replays are served only while the credential is still valid: a revoked token cannot retrieve past responses. The same key with a different body is a **422**; a concurrent duplicate while the first attempt is in flight is a **409**. A pending attempt that never completed (crashed client, mid-request deploy) stops blocking its key after about 60 seconds, so a retry of a dead request goes through rather than 409ing for the full 24h.\n\n**Rate limits.** 600 requests per 5 minutes **per API token** (or per client IP for session/unauthenticated calls), reported in the `RateLimit-Limit`, `RateLimit-Remaining` and `RateLimit-Reset` headers; **429** when exceeded. A coarser per-address ceiling also applies to token traffic.\n\n**MCP.** Agents can also connect over MCP at `/mcp` with the same `tf_` token. It exposes no capability beyond this REST API: every tool calls these endpoints under the same tenancy rules, and each tool maps to the same `read`/`write`/`delete` scopes. See \"Connecting an external agent\".\n\n**Live updates.** `wss://<host>/ws?token=<tf_… or session JWT>` (the token needs the `read` scope). Send `{\"type\":\"subscribe\",\"projectId\":\"…\"}` for each project you're a member of; you'll receive `task:created` / `task:updated` frames. For reliable sync prefer the **Events** feed or **Webhooks**: the socket is a lightweight freshness signal, not a durable stream."},"servers":[{"url":"/api/v1","description":"This instance"}],"tags":[{"name":"Instance","description":"Public, unauthenticated metadata about this deployment."},{"name":"Auth","description":"Sessions for human clients. Machine clients should use a personal API token instead (see **API tokens**)."},{"name":"API tokens","description":"Long-lived `tf_` bearer tokens with `read` / `write` / `delete` scopes. Mint one in Developers → API tokens or via `POST /tokens`."},{"name":"Workspaces","description":"The tenancy boundary. Everything else lives inside a workspace."},{"name":"Projects","description":"Projects hold sections and tasks; membership (owner / editor / viewer) is per project."},{"name":"Sections","description":"Columns within a project. Names like \"Done\" or \"In progress\" imply a task status."},{"name":"Tasks","description":"Tasks, subtasks, dependencies and cascading date shifts."},{"name":"Comments","description":"Threaded discussion on a task, with @mentions."},{"name":"Watchers","description":"Who gets notified about a task."},{"name":"Attachments","description":"Files on tasks, plus agent-friendly text extraction."},{"name":"Reports","description":"Server-rendered exports of tasks as XLSX, CSV, PDF or Markdown. The assistant's `create_report` tool and this endpoint share one engine; files are kept for 30 days."},{"name":"Tags & categories","description":"Workspace-level labels applied to tasks."},{"name":"Events","description":"The change feed: every create/update/delete in a workspace as a cursor-paginated stream. Poll it for one-way sync, or let webhooks push it to you."},{"name":"Service accounts","description":"Machine identities owned by a workspace: integrations act under their own name instead of a person's. Owner-managed; they hold their own scoped tf_ tokens."},{"name":"Webhooks","description":"HMAC-signed push delivery of the events stream to your endpoint, with retries and a delivery log. Managed by workspace owners."},{"name":"Users","description":"People lookup (always scoped to a project or workspace) and your own profile."},{"name":"Notifications","description":"In-app notification inbox."},{"name":"Email preferences","description":"Digest and notification email settings. (The one-click unsubscribe link in emails is a public root URL, `/unsubscribe/{token}`, outside this API.)"},{"name":"Invites","description":"Public, token-gated endpoints used by emailed project invites."},{"name":"Assistant memory","description":"What the built-in AI assistant remembers about you: inspect, edit, export, delete."}],"security":[{"bearerAuth":[]}],"components":{"securitySchemes":{"bearerAuth":{"type":"http","scheme":"bearer","description":"A personal API token (`tf_…`) or a session JWT."}},"schemas":{"Error":{"type":"object","properties":{"error":{"type":"string","description":"Human-readable explanation. Stable enough to show to users; not a machine code."},"details":{"type":"array","items":{"type":"object","properties":{"path":{"type":"string","description":"Dot-joined field path, e.g. `tasks.0.due_date`."},"message":{"type":"string"}},"required":["path","message"]},"description":"Present on validation failures (400): one entry per invalid field."}},"required":["error"]},"TaskStatus":{"type":"string","enum":["not_started","in_progress","blocked","completed"],"description":"Lifecycle state. `blocked` is set automatically while a dependency is incomplete."},"TaskPriority":{"type":"string","enum":["none","low","medium","high","urgent"]},"ProjectStatus":{"type":"string","enum":["active","archived","template"]},"UserRole":{"type":"string","enum":["admin","member","guest"],"description":"Instance-level role. `guest` users are restricted to projects they were invited to."},"TokenScope":{"type":"string","enum":["read","write","delete"],"description":"What a personal API token may do. GET needs `read`; writes need `write`; deleting a project needs `delete`."},"DueDateType":{"type":"string","enum":["fixed","relative"],"description":"`relative` tasks derive their due date from another task plus an offset."},"DependencyType":{"type":"string","enum":["finish_to_start","start_to_start","finish_to_finish"]},"Person":{"type":["object","null"],"properties":{"id":{"type":"string","format":"uuid"},"full_name":{"type":"string"},"avatar_url":{"type":["string","null"],"format":"uri"}},"required":["id","full_name"]},"Tag":{"type":"object","properties":{"id":{"type":"string","format":"uuid"},"name":{"type":"string"},"color":{"type":"string","description":"Hex colour, e.g. `#3b82f6`."}},"required":["id","name","color"]},"Task":{"type":"object","properties":{"id":{"type":"string","format":"uuid"},"title":{"type":"string","maxLength":255},"description":{"type":["string","null"]},"parent_task_id":{"type":["string","null"],"format":"uuid"},"parent_task":{"type":["object","null"],"properties":{"id":{"type":"string","format":"uuid"},"title":{"type":"string"}},"required":["id","title"]},"created_via":{"type":"string","description":"Which surface created the task: `app`, `mcp`, `assistant`, `api` …"},"ai_touched_at":{"type":["string","null"],"format":"date-time","description":"Last time an AI agent created or edited this task."},"nesting_level":{"type":"integer","minimum":0,"maximum":5,"description":"0 = top-level; subtasks may nest 5 deep."},"position":{"type":"number","description":"Sort key within the section (ascending)."},"primary_project_id":{"type":"string","format":"uuid"},"section_id":{"type":["string","null"],"format":"uuid"},"assignee_id":{"type":["string","null"],"format":"uuid"},"assignee":{"type":["object","null"],"properties":{"id":{"type":"string","format":"uuid"},"full_name":{"type":"string"},"avatar_url":{"type":["string","null"],"format":"uri"}},"required":["id","full_name"]},"created_by":{"type":"string","format":"uuid"},"creator":{"type":["object","null"],"properties":{"id":{"type":"string","format":"uuid"},"full_name":{"type":"string"},"avatar_url":{"type":["string","null"],"format":"uri"}},"required":["id","full_name"]},"start_date":{"type":["string","null"],"format":"date","description":"Calendar date, YYYY-MM-DD"},"due_date":{"type":["string","null"],"format":"date","description":"Calendar date, YYYY-MM-DD"},"due_date_type":{"type":"string","enum":["fixed","relative"],"description":"`relative` tasks derive their due date from another task plus an offset."},"relative_offset_days":{"type":["integer","null"]},"relative_offset_from_task_id":{"type":["string","null"],"format":"uuid"},"recurrence":{"type":["string","null"],"enum":["daily","weekly","fortnightly","monthly"]},"next_occurrence_id":{"type":["string","null"],"format":"uuid","description":"Set once this completion has spawned the next occurrence of a recurring task."},"completed_at":{"type":["string","null"],"format":"date-time"},"category":{"type":["string","null"],"maxLength":80},"priority":{"$ref":"#/components/schemas/TaskPriority"},"status":{"$ref":"#/components/schemas/TaskStatus"},"subtask_count":{"type":"integer"},"comment_count":{"type":"integer"},"attachment_count":{"type":"integer"},"blocked_by_count":{"type":"integer","description":"Dependencies this task waits on."},"blocking_count":{"type":"integer","description":"Tasks waiting on this one."},"tags":{"type":"array","items":{"$ref":"#/components/schemas/Tag"}},"watchers":{"type":"array","items":{"type":"object","properties":{"user_id":{"type":"string","format":"uuid"},"full_name":{"type":"string"},"avatar_url":{"type":["string","null"],"format":"uri"}},"required":["user_id","full_name"]}},"created_at":{"type":"string","format":"date-time"},"updated_at":{"type":"string","format":"date-time"}},"required":["id","title","primary_project_id","status","priority","created_at","updated_at"]},"Subtask":{"type":"object","properties":{"id":{"type":"string","format":"uuid"},"title":{"type":"string"},"status":{"$ref":"#/components/schemas/TaskStatus"},"priority":{"$ref":"#/components/schemas/TaskPriority"},"due_date":{"type":["string","null"],"format":"date","description":"Calendar date, YYYY-MM-DD"},"assignee_id":{"type":["string","null"],"format":"uuid"},"assignee":{"type":["object","null"],"properties":{"id":{"type":"string","format":"uuid"},"full_name":{"type":"string"},"avatar_url":{"type":["string","null"],"format":"uri"}},"required":["id","full_name"]},"subtask_count":{"type":"integer"},"position":{"type":"number"},"section_id":{"type":["string","null"],"format":"uuid"},"primary_project_id":{"type":"string","format":"uuid"},"parent_task_id":{"type":["string","null"],"format":"uuid"},"nesting_level":{"type":"integer"},"created_at":{"type":"string","format":"date-time"},"updated_at":{"type":"string","format":"date-time"}},"required":["id","title","status","priority"]},"DependencyEdge":{"type":"object","properties":{"dep_id":{"type":"string","format":"uuid","description":"The dependency row id; pass to DELETE …/dependencies/{dependencyId}."},"id":{"type":"string","format":"uuid","description":"The other task's id."},"title":{"type":"string"},"status":{"$ref":"#/components/schemas/TaskStatus"}},"required":["dep_id","id","title","status"]},"Comment":{"type":"object","properties":{"id":{"type":"string","format":"uuid"},"task_id":{"type":"string","format":"uuid"},"author_id":{"type":"string","format":"uuid"},"author":{"type":["object","null"],"properties":{"id":{"type":"string","format":"uuid"},"full_name":{"type":"string"},"avatar_url":{"type":["string","null"],"format":"uri"}},"required":["id","full_name"]},"content":{"type":"string"},"mentions":{"type":"array","items":{"type":"string","format":"uuid"},"description":"User ids mentioned. Non-members are silently dropped on create."},"edited":{"type":"boolean"},"attachments":{"type":"array","items":{"$ref":"#/components/schemas/Attachment"},"description":"Only present on GET /tasks/{id}."},"created_at":{"type":"string","format":"date-time"},"updated_at":{"type":"string","format":"date-time"}},"required":["id","task_id","author_id","content","mentions","edited","created_at","updated_at"]},"Attachment":{"type":"object","properties":{"id":{"type":"string","format":"uuid"},"task_id":{"type":"string","format":"uuid"},"comment_id":{"type":["string","null"],"format":"uuid"},"filename":{"type":"string"},"file_size":{"type":"integer","description":"Bytes."},"mime_type":{"type":"string"},"url":{"type":"string","description":"Relative download path (`/uploads/…`); requires the same Authorization header."},"uploaded_by":{"type":"string","format":"uuid"},"created_at":{"type":"string","format":"date-time"}},"required":["id","task_id","filename","file_size","mime_type","url","uploaded_by","created_at"]},"AttachmentSummary":{"type":"object","properties":{"id":{"type":"string","format":"uuid"},"filename":{"type":"string"},"file_size":{"type":"integer"},"mime_type":{"type":"string"},"uploaded_by_name":{"type":["string","null"]},"created_at":{"type":"string","format":"date-time"}},"required":["id","filename","file_size","mime_type","created_at"]},"Activity":{"type":"object","properties":{"id":{"type":"string","format":"uuid"},"action":{"type":"string","description":"`created`, `updated`, `completed`, `reopened`, `commented`, `dependency_added`, `dependency_removed`, `date_shift_applied` …"},"via":{"type":["string","null"],"description":"Set when an AI agent performed the action."},"actor":{"type":["object","null"],"properties":{"id":{"type":"string","format":"uuid"},"full_name":{"type":"string"},"avatar_url":{"type":["string","null"],"format":"uri"}},"required":["id","full_name"]},"old_value":{"anyOf":[{},{"type":"null"}]},"new_value":{"anyOf":[{},{"type":"null"}]},"created_at":{"type":"string","format":"date-time"}},"required":["id","action","created_at"]},"Watcher":{"type":"object","properties":{"user_id":{"type":"string","format":"uuid"},"full_name":{"type":"string"},"avatar_url":{"type":["string","null"],"format":"uri"}},"required":["user_id","full_name"]},"Section":{"type":"object","properties":{"id":{"type":"string","format":"uuid"},"project_id":{"type":"string","format":"uuid"},"name":{"type":"string","maxLength":255},"position":{"type":"number"},"collapsed":{"type":"boolean"},"created_at":{"type":"string","format":"date-time"},"updated_at":{"type":"string","format":"date-time"}},"required":["id","project_id","name","position","collapsed"]},"TaskDetail":{"allOf":[{"$ref":"#/components/schemas/Task"},{"type":"object","properties":{"subtasks":{"type":"array","items":{"$ref":"#/components/schemas/Subtask"}},"dependencies":{"type":"object","properties":{"blocking":{"type":"array","items":{"$ref":"#/components/schemas/DependencyEdge"},"description":"Tasks that depend on this one."},"blocked_by":{"type":"array","items":{"$ref":"#/components/schemas/DependencyEdge"},"description":"Tasks this one depends on."}},"required":["blocking","blocked_by"]},"comments":{"type":"array","items":{"$ref":"#/components/schemas/Comment"}},"attachments":{"type":"array","items":{"$ref":"#/components/schemas/Attachment"},"description":"Task-level files only (comment attachments appear under each comment)."},"activity":{"type":"array","items":{"$ref":"#/components/schemas/Activity"}},"watchers":{"type":"array","items":{"type":"object","properties":{"user_id":{"type":"string","format":"uuid"},"full_name":{"type":"string"},"avatar_url":{"type":["string","null"],"format":"uri"}},"required":["user_id","full_name"]}}},"required":["subtasks","dependencies","comments","attachments","activity","watchers"]}]},"DateShiftProposal":{"type":"object","properties":{"task_id":{"type":"string","format":"uuid"},"task_title":{"type":"string"},"current_due_date":{"type":["string","null"],"format":"date","description":"Calendar date, YYYY-MM-DD"},"new_due_date":{"type":"string","format":"date","description":"Calendar date, YYYY-MM-DD"},"shift_days":{"type":"integer"}},"required":["task_id","task_title","new_due_date","shift_days"]},"ProjectMember":{"type":"object","properties":{"user_id":{"type":"string","format":"uuid"},"role":{"type":"string","enum":["owner","editor","viewer"],"description":"Project role. Owners manage settings/members; editors change tasks; viewers read and comment."},"joined_at":{"type":"string","format":"date-time"},"user":{"type":["object","null"],"properties":{"id":{"type":"string","format":"uuid"},"full_name":{"type":"string"},"avatar_url":{"type":["string","null"],"format":"uri"}},"required":["id","full_name"]}},"required":["user_id","role","joined_at"]},"Project":{"type":"object","properties":{"id":{"type":"string","format":"uuid"},"workspace_id":{"type":"string","format":"uuid"},"name":{"type":"string","maxLength":255},"description":{"type":["string","null"]},"owner_id":{"type":"string","format":"uuid"},"color":{"type":"string","pattern":"^#[0-9A-Fa-f]{6}$"},"status":{"$ref":"#/components/schemas/ProjectStatus"},"default_view":{"type":"string","enum":["list","board","timeline","calendar"]},"task_count":{"type":"integer","description":"Present on list and get; absent on create/update responses."},"members":{"type":"array","items":{"$ref":"#/components/schemas/ProjectMember"}},"created_at":{"type":"string","format":"date-time"},"updated_at":{"type":"string","format":"date-time"}},"required":["id","workspace_id","name","owner_id","color","status","default_view","members","created_at","updated_at"]},"Invite":{"type":"object","properties":{"id":{"type":"string","format":"uuid"},"email":{"type":"string","format":"email"},"role":{"type":"string","enum":["owner","editor","viewer"]},"createdAt":{"type":"string","format":"date-time"},"expiresAt":{"type":"string","format":"date-time"}},"required":["id","email","role","createdAt","expiresAt"],"description":"Pending invite (camelCase keys)."},"WorkspaceAgent":{"type":"object","properties":{"id":{"type":"string","format":"uuid"},"name":{"type":"string","maxLength":80},"avatar_url":{"type":["string","null"]},"project_count":{"type":"integer","description":"Projects the agent is seated in."},"token_count":{"type":"integer","description":"API tokens minted for the agent."},"created_at":{"type":"string","format":"date-time"}},"required":["id","name","avatar_url","project_count","token_count","created_at"],"description":"The workspace agent seat: a bot identity assistant writes are attributed to and tasks can be assigned to."},"ImportPreview":{"type":"object","properties":{"project_name":{"type":"string"},"sections":{"type":"integer"},"tasks":{"type":"integer"},"subtasks":{"type":"integer"},"tags":{"type":"integer"},"assignees_matched":{"type":"integer","description":"Distinct assignee emails matched to people already in the workspace."},"assignees_unmatched":{"type":"array","items":{"type":"string","format":"email"}},"warnings":{"type":"array","items":{"type":"string"}}},"required":["project_name","sections","tasks","subtasks","tags","assignees_matched","assignees_unmatched","warnings"],"description":"What an import would create (or did create), with any parse warnings."},"InvitePreview":{"type":"object","properties":{"token":{"type":"string"},"email":{"type":"string","format":"email"},"role":{"type":"string","enum":["owner","editor","viewer"]},"project_name":{"type":"string"},"inviter_name":{"type":"string"},"is_new_user":{"type":"boolean","description":"`true` if no account exists for the email yet; the acceptor must supply `name`."}},"required":["token","email","role","project_name","inviter_name","is_new_user"]},"Workspace":{"type":"object","properties":{"id":{"type":"string","format":"uuid"},"name":{"type":"string","maxLength":100},"owner_id":{"type":"string","format":"uuid"},"memory_enabled":{"type":"boolean","description":"Whether the AI assistant may store memories for this workspace."}},"required":["id","name","owner_id","memory_enabled"]},"UserSummary":{"type":"object","properties":{"id":{"type":"string","format":"uuid"},"full_name":{"type":"string"},"avatar_url":{"type":["string","null"],"format":"uri"},"email":{"type":"string","format":"email"},"job_role":{"type":["string","null"],"maxLength":80,"description":"Free-text job role, e.g. `Commercial Director`."}},"required":["id","full_name","email"]},"User":{"type":"object","properties":{"id":{"type":"string","format":"uuid"},"email":{"type":"string","format":"email"},"full_name":{"type":"string"},"avatar_url":{"type":["string","null"],"format":"uri"},"job_role":{"type":["string","null"],"maxLength":80,"description":"Free-text job role, e.g. `Commercial Director`."},"role":{"$ref":"#/components/schemas/UserRole"},"status":{"type":"string","enum":["active","inactive"]},"timezone":{"type":"string","description":"IANA zone name, e.g. `Europe/London`."}},"required":["id","email","full_name","role","status","timezone"]},"WorkspaceTag":{"type":"object","properties":{"id":{"type":"string","format":"uuid"},"workspaceId":{"type":"string","format":"uuid"},"name":{"type":"string","maxLength":100},"color":{"type":"string","pattern":"^#[0-9A-Fa-f]{6}$"},"createdAt":{"type":"string","format":"date-time"}},"required":["id","workspaceId","name","color","createdAt"],"description":"Workspace-level tag definition (camelCase keys). Tasks embed the slimmer `Tag` shape."},"Category":{"type":"object","properties":{"id":{"type":"string","format":"uuid"},"workspaceId":{"type":"string","format":"uuid"},"name":{"type":"string","maxLength":80},"createdAt":{"type":"string","format":"date-time"}},"required":["id","workspaceId","name","createdAt"],"description":"Free-text task category defined per workspace (camelCase keys)."},"SearchResults":{"type":"object","properties":{"tasks":{"type":"array","items":{"type":"object","properties":{"id":{"type":"string","format":"uuid"},"title":{"type":"string"},"status":{"$ref":"#/components/schemas/TaskStatus"},"priority":{"$ref":"#/components/schemas/TaskPriority"},"primary_project_id":{"type":"string","format":"uuid"},"due_date":{"type":["string","null"],"format":"date-time"},"project_name":{"type":"string"},"project_color":{"type":"string"}},"required":["id","title","status","priority","primary_project_id","project_name","project_color"]},"description":"Up to 8 top-level tasks, most recently updated first."},"projects":{"type":"array","items":{"type":"object","properties":{"id":{"type":"string","format":"uuid"},"name":{"type":"string"},"color":{"type":"string"},"task_count":{"type":"integer"}},"required":["id","name","color","task_count"]},"description":"Up to 5 projects."}},"required":["tasks","projects"]},"AuthUser":{"type":"object","properties":{"id":{"type":"string","format":"uuid"},"email":{"type":"string","format":"email"},"full_name":{"type":"string"},"avatar_url":{"type":["string","null"],"format":"uri"},"job_role":{"type":["string","null"],"maxLength":80},"role":{"$ref":"#/components/schemas/UserRole"},"status":{"type":"string","enum":["active","inactive"]},"timezone":{"type":"string"},"onboarding_completed_at":{"type":["string","null"],"format":"date-time"}},"required":["id","email","full_name","role","status","timezone"]},"AuthWorkspace":{"type":"object","properties":{"id":{"type":"string","format":"uuid"},"name":{"type":"string"},"owner_id":{"type":"string","format":"uuid"},"memory_enabled":{"type":"boolean"},"project_count":{"type":"integer"}},"required":["id","name","owner_id","memory_enabled","project_count"]},"Session":{"type":"object","properties":{"user":{"$ref":"#/components/schemas/AuthUser"},"workspaces":{"type":"array","items":{"$ref":"#/components/schemas/AuthWorkspace"}},"token":{"type":"string","description":"Access JWT (15 min). Send as `Authorization: Bearer …`."},"refreshToken":{"type":"string","description":"Refresh JWT (30 days). Exchange at POST /auth/refresh."}},"required":["user","workspaces","token","refreshToken"]},"Message":{"type":"object","properties":{"message":{"type":"string"}},"required":["message"]},"ApiToken":{"type":"object","properties":{"id":{"type":"string","format":"uuid"},"name":{"type":"string","maxLength":80},"scopes":{"type":"array","items":{"$ref":"#/components/schemas/TokenScope"}},"lastUsedAt":{"type":["string","null"],"format":"date-time"},"createdAt":{"type":"string","format":"date-time"}},"required":["id","name","scopes","createdAt"],"description":"Personal API token metadata (camelCase keys). The secret is never returned after creation."},"Notification":{"type":"object","properties":{"id":{"type":"string","format":"uuid"},"type":{"type":"string","description":"`task_assigned`, `task_completed`, `comment_added`, `mention`, `dependency_unblocked`, `attachment_added` …"},"message":{"type":"string"},"related_task_id":{"type":["string","null"],"format":"uuid"},"related_user_id":{"type":["string","null"],"format":"uuid"},"read":{"type":"boolean"},"created_at":{"type":"string","format":"date-time"}},"required":["id","type","message","read","created_at"]},"EmailPreferences":{"type":"object","properties":{"email_enabled":{"type":"boolean"},"digest_frequency":{"type":"string","enum":["daily_weekdays","weekly","never"]},"digest_day":{"type":"integer","minimum":0,"maximum":6,"description":"0 = Sunday. Used by weekly digests."},"digest_hour":{"type":"integer","minimum":0,"maximum":23,"description":"Local hour (user timezone) to send digests."},"unsubscribed_at":{"type":["string","null"],"format":"date-time"}},"required":["email_enabled","digest_frequency","digest_day","digest_hour"]},"MemoryKind":{"type":"string","enum":["user_preference","team_convention","project_fact","entity_relationship","agent_feedback"]},"Memory":{"type":"object","properties":{"id":{"type":"string","format":"uuid"},"workspaceId":{"type":"string","format":"uuid"},"scope":{"type":"string","enum":["user","workspace","project"],"description":"`user` = private to the subject; `workspace`/`project` = shared team memory, which goes through propose-then-approve."},"kind":{"$ref":"#/components/schemas/MemoryKind"},"content":{"type":"string","maxLength":240},"importance":{"type":"integer","minimum":1,"maximum":5},"status":{"type":"string","enum":["active","proposed","superseded"],"description":"`proposed` rows are excluded from recall until approved."},"authorType":{"type":"string","enum":["user","agent"]},"sourceNote":{"type":["string","null"]},"projectId":{"type":["string","null"],"format":"uuid"},"supersededById":{"type":["string","null"],"format":"uuid"},"subjectUserId":{"type":["string","null"],"format":"uuid","description":"Who the memory is about; the subject can always see and delete it."},"authorUserId":{"type":["string","null"],"format":"uuid"},"createdAt":{"type":"string","format":"date-time"},"updatedAt":{"type":"string","format":"date-time"},"lastUsedAt":{"type":"string","format":"date-time"}},"required":["id","workspaceId","scope","kind","content","importance","authorType","createdAt"],"description":"An assistant memory (camelCase keys)."},"EventType":{"type":"string","enum":["task.created","task.updated","task.completed","task.reopened","task.deleted","task.dependency_added","task.dependency_removed","comment.created","comment.updated","comment.deleted","attachment.added","attachment.deleted","section.created","section.updated","section.deleted","project.created","project.updated","project.deleted","project.member_added","project.member_removed","tag.created","tag.deleted","category.created","category.deleted","workspace.agent_enabled","workspace.agent_disabled"],"description":"Dotted `<resource>.<verb>` names. New types may be added; consumers should ignore unknown ones."},"Event":{"type":"object","properties":{"id":{"type":"string","format":"uuid"},"cursor":{"type":"string","description":"Opaque, monotonically increasing position. Pass the last one you processed as `since`."},"type":{"$ref":"#/components/schemas/EventType"},"occurred_at":{"type":"string","format":"date-time"},"workspace_id":{"type":"string","format":"uuid"},"project_id":{"type":["string","null"],"format":"uuid","description":"Null for workspace-level events (tags, categories)."},"task_id":{"type":["string","null"],"format":"uuid"},"actor":{"type":["object","null"],"properties":{"id":{"type":"string","format":"uuid"},"full_name":{"type":"string"},"avatar_url":{"type":["string","null"],"format":"uri"}},"required":["id","full_name"]},"via":{"type":["string","null"],"enum":["assistant","mcp"],"description":"Set when an AI channel performed the action on the actor's behalf; null for direct actions."},"data":{"type":"object","additionalProperties":true,"description":"Type-specific payload. `task.*` events carry `task` (the same shape as GET /tasks/{id} minus sub-collections) and, for `task.updated`, `changes` = `{ field: { old, new } }` plus an optional `cause` (`dependency_completed`, `dependency_removed`, `date_shift`). `comment.*` carry `comment`; `section.*` carry `section`; `project.*` carry `project` / `member`; `attachment.*` carry `attachment`; `tag.*` / `category.*` carry `tag` / `category`. `*.deleted` payloads contain identifiers and names only."}},"required":["id","cursor","type","occurred_at","workspace_id","data"]},"Webhook":{"type":"object","properties":{"id":{"type":"string","format":"uuid"},"url":{"type":"string","format":"uri","maxLength":2000},"description":{"type":["string","null"],"maxLength":200},"event_types":{"type":"array","items":{"$ref":"#/components/schemas/EventType"},"description":"Empty array = subscribed to every type."},"active":{"type":"boolean"},"cursor":{"type":"string","description":"Stream position of the last delivered event."},"failure_count":{"type":"integer","description":"Consecutive failed deliveries; resets to 0 on success."},"next_attempt_at":{"type":["string","null"],"format":"date-time","description":"When the next retry is due while backing off."},"disabled_reason":{"type":["string","null"],"description":"Set when the subscription was auto-disabled (e.g. 20 consecutive failures)."},"last_delivery_at":{"type":["string","null"],"format":"date-time"},"created_at":{"type":"string","format":"date-time"}},"required":["id","url","event_types","active","cursor","failure_count","created_at"]},"WebhookDeliveryLog":{"type":"object","properties":{"id":{"type":"string","format":"uuid"},"event_id":{"type":"string"},"event_seq":{"type":"integer"},"event_type":{"type":"string"},"attempt":{"type":"integer"},"status":{"type":"string","enum":["success","failed"]},"response_status":{"type":["integer","null"]},"error":{"type":["string","null"]},"duration_ms":{"type":["integer","null"]},"created_at":{"type":"string","format":"date-time"}},"required":["id","event_id","event_type","attempt","status","created_at"]},"ServiceAccountToken":{"type":"object","properties":{"id":{"type":"string","format":"uuid"},"name":{"type":"string","maxLength":80},"scopes":{"type":"array","items":{"$ref":"#/components/schemas/TokenScope"}},"lastUsedAt":{"type":["string","null"],"format":"date-time"},"createdAt":{"type":"string","format":"date-time"}},"required":["id","name","scopes","createdAt"],"description":"Token metadata (camelCase keys). The tf_ plaintext appears only in the mint response."},"ServiceAccount":{"type":"object","properties":{"id":{"type":"string","format":"uuid","description":"Also the actor id this identity appears under in events, activity and audit."},"name":{"type":"string","maxLength":80,"description":"Display name: what shows up as the actor, e.g. \"CRM sync\" or \"Reporting bot\"."},"status":{"type":"string","enum":["active","inactive"]},"projects":{"type":"array","items":{"type":"object","properties":{"id":{"type":"string","format":"uuid"},"name":{"type":"string"}},"required":["id","name"]},"description":"Projects it can act in (as an editor; the token scopes can restrict further)."},"tokens":{"type":"array","items":{"$ref":"#/components/schemas/ServiceAccountToken"}},"created_at":{"type":"string","format":"date-time"}},"required":["id","name","status","projects","tokens","created_at"]},"AllowedOrigin":{"type":"object","properties":{"id":{"type":"string","format":"uuid"},"origin":{"type":"string","description":"Normalised origin: scheme + host (+ port), e.g. `https://app.example.com`."},"created_at":{"type":"string","format":"date-time"}},"required":["id","origin","created_at"]},"ReportSpec":{"type":"object","properties":{"template":{"type":"string","enum":["task_list","status_report","workload"],"default":"task_list","description":"`task_list`: rows of tasks. `status_report`: headline figures, an optional narrative and tasks by section (needs `project_id`). `workload`: open tasks per person."},"format":{"type":"string","enum":["xlsx","csv","pdf","md"],"default":"xlsx"},"title":{"type":"string","maxLength":120,"description":"File and document title; defaults to the project or workspace name plus the template."},"narrative":{"type":"string","maxLength":4000,"description":"Short prose placed above the tables (blank-line separated paragraphs)."},"project_id":{"type":"string","format":"uuid","description":"One project. Either this or `workspace_id` is required."},"workspace_id":{"type":"string","format":"uuid","description":"A cross-project report over one workspace; ignored when `project_id` is set."},"filters":{"type":"object","properties":{"status":{"type":"string","enum":["any","not_started","in_progress","blocked","completed"]},"assignee_id":{"type":"string","format":"uuid"},"priority":{"type":"string","enum":["any","low","medium","high","urgent"]},"due_before":{"type":"string","format":"date","description":"Calendar date, YYYY-MM-DD"},"due_after":{"type":"string","format":"date","description":"Calendar date, YYYY-MM-DD"},"exclude_completed":{"type":"boolean","description":"Defaults to true for `workload`, false otherwise."},"include_subtasks":{"type":"boolean"}}},"group_by":{"type":"string","enum":["section","assignee","status","priority","project","none"],"description":"Defaults to `section` for a project, `project` for a workspace."},"columns":{"type":"array","items":{"type":"string","enum":["title","project","section","status","priority","assignee","start_date","due_date","description","tags","subtasks","comments","attachments","url"]},"description":"Column pick for `task_list`; default omits description and url."},"deliver_to":{"type":"string","enum":["chat","task","email"],"default":"chat","description":"`chat`: stored only. `task`: also attached to `task_id`. `email`: also emailed to the caller (never anyone else)."},"task_id":{"type":"string","format":"uuid","description":"Required when `deliver_to` is `task`."}},"additionalProperties":false},"ReportFile":{"type":"object","properties":{"id":{"type":"string","format":"uuid"},"filename":{"type":"string"},"mime_type":{"type":"string"},"file_size":{"type":"integer","description":"Bytes."},"row_count":{"type":"integer"},"template":{"type":"string","enum":["task_list","status_report","workload"]},"format":{"type":"string","enum":["xlsx","csv","pdf","md"]},"delivered_to":{"type":"string","enum":["chat","task","email"]},"url":{"type":"string","description":"Relative download path; send your bearer token."},"download_url":{"type":["string","null"],"description":"Absolute link that works WITHOUT a bearer token for 24 hours (signed, bound to this file). Null when the instance cannot sign links."},"created_at":{"type":"string","format":"date-time"},"expires_at":{"type":"string","format":"date-time","description":"Files are deleted after 30 days."},"attachment":{"type":"object","properties":{"id":{"type":"string","format":"uuid"},"task_id":{"type":"string","format":"uuid"},"task_url":{"type":["string","null"]}},"required":["id","task_id"],"description":"Present when delivered to a task."},"email":{"type":"object","properties":{"sent":{"type":"boolean"},"to":{"type":"string"},"note":{"type":"string"}},"required":["sent"],"description":"Present when delivery was email; `note` says why it was not sent."}},"required":["id","filename","mime_type","file_size","row_count","template","format","delivered_to","url","download_url","created_at","expires_at"]}}},"paths":{"/config/public":{"get":{"operationId":"getPublicConfig","summary":"Public instance settings","description":"Whether self-service sign-up is open and what the instance calls itself.","responses":{"200":{"description":"Config.","content":{"application/json":{"schema":{"type":"object","properties":{"signup_enabled":{"type":"boolean"},"app_name":{"type":"string"}},"required":["signup_enabled","app_name"]}}}}},"tags":["Instance"],"security":[]}},"/health":{"get":{"operationId":"health","summary":"Liveness check","responses":{"200":{"description":"Alive.","content":{"application/json":{"schema":{"type":"object","properties":{"status":{"type":"string","enum":["ok"]},"ts":{"type":"string","format":"date-time"}},"required":["status","ts"]}}}}},"tags":["Instance"],"security":[]}},"/openapi.json":{"get":{"operationId":"getOpenApiDocument","summary":"This document","responses":{"200":{"description":"OpenAPI 3.1 document.","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}}},"tags":["Instance"],"security":[]}},"/auth/register":{"post":{"operationId":"register","summary":"Create an account","description":"Only when public sign-up is enabled on the instance. Sends a 24-hour verification email; the response is identical whether or not the address was already registered. Disposable email domains are refused. No session is issued: verify first, then log in. When the instance has reached its signup cap, no account is created and the response is `{ \"at_capacity\": true }`; offer the waitlist (`POST /waitlist`) instead.","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"name":{"type":"string","minLength":1,"maxLength":100},"email":{"type":"string","format":"email"},"password":{"type":"string","minLength":8,"description":"At least 8 chars with an uppercase letter, a lowercase letter, a number and one of `!@#$%^&*`."},"src":{"type":"string","maxLength":64,"description":"Optional signup-source tag from a `?src=` on the register link (e.g. `registry`, `hn`, `email`). Normalized to an allowlist and stored on the account; unrecognised values become `other`, absent becomes `direct`."}},"required":["name","email","password"]}}}},"responses":{"200":{"description":"Accepted, or `{ at_capacity: true }` when the signup cap is reached.","content":{"application/json":{"schema":{"type":"object","properties":{"message":{"type":"string"},"at_capacity":{"type":"boolean"}}}}}},"400":{"description":"Validation failed: the body or query did not match the schema, or a business rule was violated. `error` says which.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Public registration is disabled.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Too many sign-ups from this IP. Try again later.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}},"tags":["Auth"],"security":[]}},"/waitlist":{"post":{"operationId":"joinWaitlist","summary":"Join the early-access waitlist","description":"Only when public sign-up is enabled. Idempotent; the response is identical whether or not the address was already on the list. The register page offers this when the instance has reached its signup cap.","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"email":{"type":"string","format":"email"}},"required":["email"]}}}},"responses":{"200":{"description":"Joined.","content":{"application/json":{"schema":{"type":"object","properties":{"joined":{"type":"boolean"}},"required":["joined"]}}}},"400":{"description":"Validation failed: the body or query did not match the schema, or a business rule was violated. `error` says which.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"The waitlist is not open on this instance.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Too many requests from this IP. Try again later.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}},"tags":["Auth"],"security":[]}},"/contact":{"post":{"operationId":"contact","summary":"Send a support message","description":"Public contact form. Delivers the message to the team inbox with reply-to set to the sender; no support address is exposed on the site. Rate-limited per IP. A spam honeypot field (`company`) is accepted but ignored — real clients leave it empty.","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"email":{"type":"string","format":"email"},"message":{"type":"string","minLength":10,"maxLength":5000},"name":{"type":"string","maxLength":100},"company":{"type":"string","maxLength":200,"description":"Honeypot — leave empty."}},"required":["email","message"]}}}},"responses":{"200":{"description":"Accepted (sent, or silently dropped if the honeypot was filled).","content":{"application/json":{"schema":{"type":"object","properties":{"sent":{"type":"boolean"}},"required":["sent"]}}}},"400":{"description":"Validation failed: the body or query did not match the schema, or a business rule was violated. `error` says which.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Too many messages from this IP. Try again later.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"502":{"description":"The message could not be sent right now.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}},"tags":["Auth"],"security":[]}},"/auth/verify-email":{"post":{"operationId":"verifyEmail","summary":"Verify an email address","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"token":{"type":"string","minLength":1}},"required":["token"]}}}},"responses":{"200":{"description":"Verified.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Message"}}}},"400":{"description":"Validation failed: the body or query did not match the schema, or a business rule was violated. `error` says which.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"410":{"description":"Invalid, used or expired link.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Too many attempts from this IP. Try again later.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}},"tags":["Auth"],"security":[]}},"/auth/resend-verification":{"post":{"operationId":"resendVerification","summary":"Resend the verification email","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"email":{"type":"string","format":"email"}},"required":["email"]}}}},"responses":{"200":{"description":"Accepted (same response whether or not an unverified account exists).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Message"}}}},"400":{"description":"Validation failed: the body or query did not match the schema, or a business rule was violated. `error` says which.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Public registration is disabled.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Too many requests from this IP. Try again later.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}},"tags":["Auth"],"security":[]}},"/auth/login":{"post":{"operationId":"login","summary":"Log in","description":"Returns the user, their workspaces and a JWT pair in the body (no cookies). For machine clients prefer a personal API token (`POST /tokens`): JWTs expire after 15 minutes.","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"email":{"type":"string","format":"email"},"password":{"type":"string","minLength":1}},"required":["email","password"]}}}},"responses":{"200":{"description":"Session.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Session"}}}},"400":{"description":"Validation failed: the body or query did not match the schema, or a business rule was violated. `error` says which.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Invalid email or password.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Email not yet verified (public instances only).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Too many login attempts from this IP. Try again later.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}},"tags":["Auth"],"security":[]}},"/auth/refresh":{"post":{"operationId":"refreshToken","summary":"Refresh the access token","description":"Exchanges a refresh token for a new 15-minute access token. The refresh token itself is not rotated.","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"refreshToken":{"type":"string"}},"required":["refreshToken"]}}}},"responses":{"200":{"description":"New access token.","content":{"application/json":{"schema":{"type":"object","properties":{"token":{"type":"string"}},"required":["token"]}}}},"400":{"description":"Validation failed: the body or query did not match the schema, or a business rule was violated. `error` says which.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Invalid, expired or invalidated refresh token.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Too many refreshes from this IP. Try again later.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}},"tags":["Auth"],"security":[]}},"/auth/logout":{"post":{"operationId":"logout","summary":"Log out everywhere","description":"Invalidates every outstanding JWT for the user. Personal API tokens deliberately survive a logout.","responses":{"204":{"description":"Logged out."},"401":{"description":"Missing, invalid, expired or revoked credentials.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Authenticated, but not allowed: not a member, insufficient project role or the API token lacks the required scope.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Rate limited. Back off and retry after the window in the `RateLimit-*` headers.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}},"tags":["Auth"],"x-token-scope":"write"}},"/auth/forgot-password":{"post":{"operationId":"forgotPassword","summary":"Request a password-reset email","description":"Always 200. The reset link is valid for 1 hour.","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"email":{"type":"string","format":"email"}},"required":["email"]}}}},"responses":{"200":{"description":"Accepted.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Message"}}}},"400":{"description":"Validation failed: the body or query did not match the schema, or a business rule was violated. `error` says which.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Too many requests from this IP. Try again later.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}},"tags":["Auth"],"security":[]}},"/auth/reset-password":{"post":{"operationId":"resetPassword","summary":"Reset the password","description":"Also invalidates every session AND every personal API token (a credential change is a security event), and marks the email verified.","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"token":{"type":"string"},"password":{"type":"string","minLength":8,"description":"At least 8 chars with an uppercase letter, a lowercase letter, a number and one of `!@#$%^&*`."}},"required":["token","password"]}}}},"responses":{"200":{"description":"Reset.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Message"}}}},"400":{"description":"Validation failed: the body or query did not match the schema, or a business rule was violated. `error` says which.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Too many attempts from this IP. Try again later.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}},"tags":["Auth"],"security":[]}},"/auth/me":{"get":{"operationId":"getSession","summary":"Who am I","description":"The same `user` and `workspaces` as login, without tokens. Works with a `tf_` token too.","responses":{"200":{"description":"Me.","content":{"application/json":{"schema":{"type":"object","properties":{"user":{"$ref":"#/components/schemas/AuthUser"},"workspaces":{"type":"array","items":{"$ref":"#/components/schemas/AuthWorkspace"}}},"required":["user","workspaces"]}}}},"401":{"description":"Missing, invalid, expired or revoked credentials.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Authenticated, but not allowed: not a member, insufficient project role or the API token lacks the required scope.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Rate limited. Back off and retry after the window in the `RateLimit-*` headers.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}},"tags":["Auth"],"x-token-scope":"read"}},"/tokens":{"get":{"operationId":"listApiTokens","summary":"List my API tokens","responses":{"200":{"description":"Tokens, newest first. Secrets are never included.","content":{"application/json":{"schema":{"type":"object","properties":{"tokens":{"type":"array","items":{"$ref":"#/components/schemas/ApiToken"}}},"required":["tokens"]}}}},"401":{"description":"Missing, invalid, expired or revoked credentials.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Authenticated, but not allowed: not a member, insufficient project role or the API token lacks the required scope.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Rate limited. Back off and retry after the window in the `RateLimit-*` headers.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}},"tags":["API tokens"],"x-token-scope":"read"},"post":{"operationId":"createApiToken","summary":"Create an API token","description":"The plaintext (`plain`, prefixed `tf_`) is returned once; store it. Default scopes are `read` + `write`. A token may not mint a token with scopes beyond its own. Max 10 per user.","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"name":{"type":"string","minLength":1,"maxLength":80},"scopes":{"type":"array","items":{"$ref":"#/components/schemas/TokenScope"},"minItems":1,"description":"Default `[\"read\",\"write\"]`."}},"required":["name"]}}}},"responses":{"201":{"description":"Created.","content":{"application/json":{"schema":{"type":"object","properties":{"token":{"allOf":[{"$ref":"#/components/schemas/ApiToken"},{"type":"object","properties":{"plain":{"type":"string","description":"The secret. Shown once."}},"required":["plain"]}]}},"required":["token"]}}}},"400":{"description":"Validation failed: the body or query did not match the schema, or a business rule was violated. `error` says which.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Missing, invalid, expired or revoked credentials.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Authenticated, but not allowed: not a member, insufficient project role or the API token lacks the required scope.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Rate limited. Back off and retry after the window in the `RateLimit-*` headers.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}},"tags":["API tokens"],"x-token-scope":"write"}},"/tokens/{id}":{"delete":{"operationId":"revokeApiToken","summary":"Revoke an API token","parameters":[{"name":"id","in":"path","required":true,"description":"Token id.","schema":{"type":"string","format":"uuid"}}],"responses":{"204":{"description":"Revoked."},"401":{"description":"Missing, invalid, expired or revoked credentials.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Authenticated, but not allowed: not a member, insufficient project role or the API token lacks the required scope.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"Not found, or not visible to the caller (tenancy is enforced: foreign resources look identical to missing ones).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Rate limited. Back off and retry after the window in the `RateLimit-*` headers.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}},"tags":["API tokens"],"x-token-scope":"write"}},"/workspaces":{"get":{"operationId":"listWorkspaces","summary":"List my workspaces","description":"Every workspace the caller owns, is a member of or has a project membership in; owned ones first.","responses":{"200":{"description":"Workspaces.","content":{"application/json":{"schema":{"type":"object","properties":{"workspaces":{"type":"array","items":{"$ref":"#/components/schemas/Workspace"}}},"required":["workspaces"]}}}},"401":{"description":"Missing, invalid, expired or revoked credentials.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Authenticated, but not allowed: not a member, insufficient project role or the API token lacks the required scope.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Rate limited. Back off and retry after the window in the `RateLimit-*` headers.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}},"tags":["Workspaces"],"x-token-scope":"read"},"post":{"operationId":"createWorkspace","summary":"Create a workspace","description":"The caller becomes the owner. Nothing else is created. New workspaces start on the free plan; on instances with billing enforcement enabled, free accounts include one workspace and creating a second returns 403.","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"name":{"type":"string","minLength":1,"maxLength":100}},"required":["name"]}}}},"responses":{"201":{"description":"Created.","content":{"application/json":{"schema":{"type":"object","properties":{"workspace":{"$ref":"#/components/schemas/Workspace"}},"required":["workspace"]}}}},"400":{"description":"Validation failed: the body or query did not match the schema, or a business rule was violated. `error` says which.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Missing, invalid, expired or revoked credentials.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Authenticated, but not allowed: not a member, insufficient project role or the API token lacks the required scope.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Rate limited. Back off and retry after the window in the `RateLimit-*` headers.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}},"tags":["Workspaces"],"x-token-scope":"write"}},"/workspaces/{workspaceId}/agent":{"get":{"operationId":"getWorkspaceAgent","summary":"Get the workspace agent seat","description":"Any member of the workspace. `agent` is null when no agent seat is enabled.","parameters":[{"name":"workspaceId","in":"path","required":true,"description":"Workspace id.","schema":{"type":"string","format":"uuid"}}],"responses":{"200":{"description":"The agent, or null.","content":{"application/json":{"schema":{"type":"object","properties":{"agent":{"anyOf":[{"$ref":"#/components/schemas/WorkspaceAgent"},{"type":"null"}]}},"required":["agent"]}}}},"401":{"description":"Missing, invalid, expired or revoked credentials.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Authenticated, but not allowed: not a member, insufficient project role or the API token lacks the required scope.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"Not found, or not visible to the caller (tenancy is enforced: foreign resources look identical to missing ones).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Rate limited. Back off and retry after the window in the `RateLimit-*` headers.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}},"tags":["Workspaces"],"x-token-scope":"read"},"post":{"operationId":"enableWorkspaceAgent","summary":"Enable the agent seat","description":"Workspace owner only. Creates (or reactivates) the workspace agent: a bot-flagged service account seated as editor in every project of the workspace, including future ones. The embedded assistant's writes are then attributed to the agent (with the delegating human recorded), tasks can be assigned to it, and tokens minted for it through the service-accounts API let an external runner act as the same identity. It never counts against the plan's people limit.","parameters":[{"name":"workspaceId","in":"path","required":true,"description":"Workspace id.","schema":{"type":"string","format":"uuid"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"name":{"type":"string","minLength":1,"maxLength":80,"description":"Display name. Defaults to \"<app name> Agent\"."}}}}}},"responses":{"201":{"description":"Enabled.","content":{"application/json":{"schema":{"type":"object","properties":{"agent":{"$ref":"#/components/schemas/WorkspaceAgent"}},"required":["agent"]}}}},"400":{"description":"Validation failed: the body or query did not match the schema, or a business rule was violated. `error` says which.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Missing, invalid, expired or revoked credentials.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Authenticated, but not allowed: not a member, insufficient project role or the API token lacks the required scope.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"Not found, or not visible to the caller (tenancy is enforced: foreign resources look identical to missing ones).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"409":{"description":"This workspace already has an active agent.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Rate limited. Back off and retry after the window in the `RateLimit-*` headers.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}},"tags":["Workspaces"],"x-token-scope":"write"},"patch":{"operationId":"renameWorkspaceAgent","summary":"Rename the agent","description":"Workspace owner only.","parameters":[{"name":"workspaceId","in":"path","required":true,"description":"Workspace id.","schema":{"type":"string","format":"uuid"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"name":{"type":"string","minLength":1,"maxLength":80}},"required":["name"]}}}},"responses":{"200":{"description":"Renamed.","content":{"application/json":{"schema":{"type":"object","properties":{"agent":{"$ref":"#/components/schemas/WorkspaceAgent"}},"required":["agent"]}}}},"400":{"description":"Validation failed: the body or query did not match the schema, or a business rule was violated. `error` says which.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Missing, invalid, expired or revoked credentials.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Authenticated, but not allowed: not a member, insufficient project role or the API token lacks the required scope.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"Not found, or not visible to the caller (tenancy is enforced: foreign resources look identical to missing ones).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Rate limited. Back off and retry after the window in the `RateLimit-*` headers.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}},"tags":["Workspaces"],"x-token-scope":"write"},"delete":{"operationId":"disableWorkspaceAgent","summary":"Retire the agent seat","description":"Workspace owner only. The agent's tokens are revoked immediately and the identity goes inactive; its history, comments and assignments remain. Enabling again reactivates the same identity.","parameters":[{"name":"workspaceId","in":"path","required":true,"description":"Workspace id.","schema":{"type":"string","format":"uuid"}}],"responses":{"204":{"description":"Retired."},"401":{"description":"Missing, invalid, expired or revoked credentials.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Authenticated, but not allowed: not a member, insufficient project role or the API token lacks the required scope.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"Not found, or not visible to the caller (tenancy is enforced: foreign resources look identical to missing ones).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Rate limited. Back off and retry after the window in the `RateLimit-*` headers.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}},"tags":["Workspaces"],"x-token-scope":"write"}},"/workspaces/{workspaceId}":{"patch":{"operationId":"renameWorkspace","summary":"Rename a workspace","description":"Owner only (non-owners see 404).","parameters":[{"name":"workspaceId","in":"path","required":true,"description":"Workspace id.","schema":{"type":"string","format":"uuid"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"name":{"type":"string","minLength":1,"maxLength":100}},"required":["name"]}}}},"responses":{"200":{"description":"Renamed.","content":{"application/json":{"schema":{"type":"object","properties":{"workspace":{"type":"object","properties":{"id":{"type":"string","format":"uuid"},"name":{"type":"string"}},"required":["id","name"]}},"required":["workspace"]}}}},"400":{"description":"Validation failed: the body or query did not match the schema, or a business rule was violated. `error` says which.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Missing, invalid, expired or revoked credentials.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Authenticated, but not allowed: not a member, insufficient project role or the API token lacks the required scope.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"Not found, or not visible to the caller (tenancy is enforced: foreign resources look identical to missing ones).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Rate limited. Back off and retry after the window in the `RateLimit-*` headers.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}},"tags":["Workspaces"],"x-token-scope":"write"},"delete":{"operationId":"deleteWorkspace","summary":"Delete a workspace","description":"Owner only. Cascades to every project, task, tag, category and AI memory inside it. Your last owned workspace cannot be deleted.","parameters":[{"name":"workspaceId","in":"path","required":true,"description":"Workspace id.","schema":{"type":"string","format":"uuid"}}],"responses":{"204":{"description":"Deleted."},"400":{"description":"Validation failed: the body or query did not match the schema, or a business rule was violated. `error` says which.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Missing, invalid, expired or revoked credentials.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Authenticated, but not allowed: not a member, insufficient project role or the API token lacks the required scope.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"Not found, or not visible to the caller (tenancy is enforced: foreign resources look identical to missing ones).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Rate limited. Back off and retry after the window in the `RateLimit-*` headers.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}},"tags":["Workspaces"],"x-token-scope":"delete"}},"/workspaces/{workspaceId}/cors-origins":{"get":{"operationId":"listAllowedOrigins","summary":"List browser origins","description":"Workspace owner only. Origins your own web apps may call this API from (see POST for the semantics).","parameters":[{"name":"workspaceId","in":"path","required":true,"description":"Workspace id.","schema":{"type":"string","format":"uuid"}}],"responses":{"200":{"description":"Origins.","content":{"application/json":{"schema":{"type":"object","properties":{"origins":{"type":"array","items":{"$ref":"#/components/schemas/AllowedOrigin"}}},"required":["origins"]}}}},"401":{"description":"Missing, invalid, expired or revoked credentials.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Authenticated, but not allowed: not a member, insufficient project role or the API token lacks the required scope.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Rate limited. Back off and retry after the window in the `RateLimit-*` headers.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}},"tags":["Workspaces"],"x-token-scope":"read"},"post":{"operationId":"addAllowedOrigin","summary":"Register a browser origin","description":"Workspace owner only, max 10 per workspace. Registering an origin makes the API answer CORS for it (without cookies; requests still need a `tf_` bearer token), so a web app served from that origin can call `/api/v1` directly from the browser. https only (http allowed for localhost); scheme + host (+ port) only, no wildcards. Takes up to a minute to propagate. Idempotent.","parameters":[{"name":"workspaceId","in":"path","required":true,"description":"Workspace id.","schema":{"type":"string","format":"uuid"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"origin":{"type":"string","maxLength":300,"description":"e.g. `https://app.example.com`"}},"required":["origin"]}}}},"responses":{"201":{"description":"Registered (or already present).","content":{"application/json":{"schema":{"type":"object","properties":{"origin":{"$ref":"#/components/schemas/AllowedOrigin"}},"required":["origin"]}}}},"400":{"description":"Validation failed: the body or query did not match the schema, or a business rule was violated. `error` says which.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Missing, invalid, expired or revoked credentials.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Authenticated, but not allowed: not a member, insufficient project role or the API token lacks the required scope.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Rate limited. Back off and retry after the window in the `RateLimit-*` headers.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}},"tags":["Workspaces"],"x-token-scope":"write"}},"/workspaces/{workspaceId}/cors-origins/{id}":{"delete":{"operationId":"removeAllowedOrigin","summary":"Remove a browser origin","parameters":[{"name":"workspaceId","in":"path","required":true,"description":"Workspace id.","schema":{"type":"string","format":"uuid"}},{"name":"id","in":"path","required":true,"description":"Origin id.","schema":{"type":"string","format":"uuid"}}],"responses":{"204":{"description":"Removed."},"401":{"description":"Missing, invalid, expired or revoked credentials.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Authenticated, but not allowed: not a member, insufficient project role or the API token lacks the required scope.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"Not found, or not visible to the caller (tenancy is enforced: foreign resources look identical to missing ones).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Rate limited. Back off and retry after the window in the `RateLimit-*` headers.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}},"tags":["Workspaces"],"x-token-scope":"write"}},"/projects":{"get":{"operationId":"listProjects","summary":"List my projects","description":"Projects the caller is a member of, newest first. Not paginated. Without a `status` filter, projects with status `template` are excluded; pass `status=template` to list templates.","parameters":[{"name":"workspace_id","in":"query","required":false,"description":"Only projects in this workspace.","schema":{"type":"string","format":"uuid"}},{"name":"status","in":"query","required":false,"description":"Only projects with this status.","schema":{"type":"string","enum":["active","archived","template"]}}],"responses":{"200":{"description":"Projects (each with `task_count`).","content":{"application/json":{"schema":{"type":"object","properties":{"projects":{"type":"array","items":{"$ref":"#/components/schemas/Project"}}},"required":["projects"]}}}},"401":{"description":"Missing, invalid, expired or revoked credentials.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Authenticated, but not allowed: not a member, insufficient project role or the API token lacks the required scope.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Rate limited. Back off and retry after the window in the `RateLimit-*` headers.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}},"tags":["Projects"],"x-token-scope":"read"},"post":{"operationId":"createProject","summary":"Create a project","description":"The caller becomes the project owner and three default sections are created: \"To Do\", \"In Progress\", \"Done\". Guests cannot create projects. Requires access to the workspace.","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"workspace_id":{"type":"string","format":"uuid"},"name":{"type":"string","minLength":1,"maxLength":255},"description":{"type":"string"},"color":{"type":"string","pattern":"^#[0-9A-Fa-f]{6}$","description":"Hex colour. Default `#4A90E2`."},"default_view":{"type":"string","enum":["list","board","timeline","calendar"],"description":"Default `list`."}},"required":["workspace_id","name"]}}}},"responses":{"201":{"description":"Created.","content":{"application/json":{"schema":{"type":"object","properties":{"project":{"$ref":"#/components/schemas/Project"}},"required":["project"]}}}},"400":{"description":"Validation failed: the body or query did not match the schema, or a business rule was violated. `error` says which.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Missing, invalid, expired or revoked credentials.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Authenticated, but not allowed: not a member, insufficient project role or the API token lacks the required scope.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Rate limited. Back off and retry after the window in the `RateLimit-*` headers.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}},"tags":["Projects"],"x-token-scope":"write"}},"/projects/import":{"post":{"operationId":"importProject","summary":"Import a project from another tool","description":"Creates a project from a Trello board JSON export (`trello`), an Asana project CSV export (`asana_csv`) or a generic CSV (`csv`, columns: Title, Description, Section, Status, Priority, Assignee Email, Start Date, Due Date, Tags, Parent Task). Sections, tasks, subtasks, priorities, dates and tags are carried over; assignee emails are matched only against people who already have access to the workspace, and matched assignees join the new project as editors. With `dry_run: true` the parsed preview (counts and warnings) is returned and nothing is created. Caps: 4MB of content, 2000 tasks, 100 sections. Guests cannot import.","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"workspace_id":{"type":"string","format":"uuid"},"source":{"type":"string","enum":["trello","asana_csv","csv"]},"content":{"type":"string","minLength":1,"maxLength":4000000,"description":"The raw export file content, as text."},"name":{"type":"string","minLength":1,"maxLength":255,"description":"Project name. Defaults to the name in the export, else \"Imported project\"."},"dry_run":{"type":"boolean","description":"Return the preview without creating anything."}},"required":["workspace_id","source","content"]}}}},"responses":{"200":{"description":"Dry run: the preview only.","content":{"application/json":{"schema":{"type":"object","properties":{"preview":{"$ref":"#/components/schemas/ImportPreview"}},"required":["preview"]}}}},"201":{"description":"Imported.","content":{"application/json":{"schema":{"type":"object","properties":{"project":{"$ref":"#/components/schemas/Project"},"preview":{"$ref":"#/components/schemas/ImportPreview"}},"required":["project","preview"]}}}},"400":{"description":"Validation failed: the body or query did not match the schema, or a business rule was violated. `error` says which.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Missing, invalid, expired or revoked credentials.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Authenticated, but not allowed: not a member, insufficient project role or the API token lacks the required scope.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Rate limited. Back off and retry after the window in the `RateLimit-*` headers.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}},"tags":["Projects"],"x-token-scope":"write"}},"/projects/{id}/duplicate":{"post":{"operationId":"duplicateProject","summary":"Duplicate a project (or save it as a template)","description":"Copies the project's structure: sections, tasks, subtasks, priorities, categories and tags. Progress, assignees, dates, comments and attachments are reset, and members are not carried over; the caller becomes the copy's owner. With `as_template: true` the copy gets status `template`, which is what the new-project template picker lists; create a project FROM a template by duplicating the template without that flag. Any project member; guests cannot duplicate.","parameters":[{"name":"id","in":"path","required":true,"description":"Project id.","schema":{"type":"string","format":"uuid"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"name":{"type":"string","minLength":1,"maxLength":255,"description":"Defaults to the source name plus \" (copy)\" or \" (template)\"."},"as_template":{"type":"boolean","description":"Create the copy with status `template`."},"include_tasks":{"type":"boolean","description":"Default true. False copies only the project and its sections."}}}}}},"responses":{"201":{"description":"Created.","content":{"application/json":{"schema":{"type":"object","properties":{"project":{"$ref":"#/components/schemas/Project"}},"required":["project"]}}}},"400":{"description":"Validation failed: the body or query did not match the schema, or a business rule was violated. `error` says which.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Missing, invalid, expired or revoked credentials.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Authenticated, but not allowed: not a member, insufficient project role or the API token lacks the required scope.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"Not found, or not visible to the caller (tenancy is enforced: foreign resources look identical to missing ones).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Rate limited. Back off and retry after the window in the `RateLimit-*` headers.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}},"tags":["Projects"],"x-token-scope":"write"}},"/projects/{id}":{"get":{"operationId":"getProject","summary":"Get a project","description":"Any project member. Non-members receive 404, not 403.","parameters":[{"name":"id","in":"path","required":true,"description":"Project id.","schema":{"type":"string","format":"uuid"}}],"responses":{"200":{"description":"The project.","content":{"application/json":{"schema":{"type":"object","properties":{"project":{"$ref":"#/components/schemas/Project"}},"required":["project"]}}}},"401":{"description":"Missing, invalid, expired or revoked credentials.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Authenticated, but not allowed: not a member, insufficient project role or the API token lacks the required scope.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"Not found, or not visible to the caller (tenancy is enforced: foreign resources look identical to missing ones).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Rate limited. Back off and retry after the window in the `RateLimit-*` headers.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}},"tags":["Projects"],"x-token-scope":"read"},"patch":{"operationId":"updateProject","summary":"Update project settings","description":"Project owner only. Archive with `status: \"archived\"`; `template` cannot be set here.","parameters":[{"name":"id","in":"path","required":true,"description":"Project id.","schema":{"type":"string","format":"uuid"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"name":{"type":"string","minLength":1,"maxLength":255},"description":{"type":"string"},"color":{"type":"string","pattern":"^#[0-9A-Fa-f]{6}$","description":"Hex colour. Default `#4A90E2`."},"default_view":{"type":"string","enum":["list","board","timeline","calendar"]},"status":{"type":"string","enum":["active","archived"]}}}}}},"responses":{"200":{"description":"Updated.","content":{"application/json":{"schema":{"type":"object","properties":{"project":{"$ref":"#/components/schemas/Project"}},"required":["project"]}}}},"400":{"description":"Validation failed: the body or query did not match the schema, or a business rule was violated. `error` says which.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Missing, invalid, expired or revoked credentials.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Authenticated, but not allowed: not a member, insufficient project role or the API token lacks the required scope.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"Not found, or not visible to the caller (tenancy is enforced: foreign resources look identical to missing ones).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Rate limited. Back off and retry after the window in the `RateLimit-*` headers.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}},"tags":["Projects"],"x-token-scope":"write"},"delete":{"operationId":"deleteProject","summary":"Delete a project","description":"Project owner only. Cascades to sections, tasks, comments, attachments, invites and memberships. A `tf_` token needs the `delete` scope.","parameters":[{"name":"id","in":"path","required":true,"description":"Project id.","schema":{"type":"string","format":"uuid"}}],"responses":{"204":{"description":"Deleted."},"401":{"description":"Missing, invalid, expired or revoked credentials.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Authenticated, but not allowed: not a member, insufficient project role or the API token lacks the required scope.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"Not found, or not visible to the caller (tenancy is enforced: foreign resources look identical to missing ones).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Rate limited. Back off and retry after the window in the `RateLimit-*` headers.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}},"tags":["Projects"],"x-token-scope":"delete"}},"/projects/{id}/members":{"post":{"operationId":"addProjectMember","summary":"Add or re-role a member","description":"Project owner only. Upserts the membership, so it also changes an existing member's role. On instances with billing enforcement enabled, adding someone new past the workspace plan's people limit returns 403; re-adding an existing person always succeeds.","parameters":[{"name":"id","in":"path","required":true,"description":"Project id.","schema":{"type":"string","format":"uuid"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"user_id":{"type":"string","format":"uuid"},"role":{"type":"string","enum":["editor","viewer"],"description":"Ownership cannot be granted through the API."}},"required":["user_id","role"]}}}},"responses":{"201":{"description":"Added.","content":{"application/json":{"schema":{"type":"object","properties":{"message":{"type":"string"}},"required":["message"]}}}},"400":{"description":"Validation failed: the body or query did not match the schema, or a business rule was violated. `error` says which.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Missing, invalid, expired or revoked credentials.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Authenticated, but not allowed: not a member, insufficient project role or the API token lacks the required scope.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"Not found, or not visible to the caller (tenancy is enforced: foreign resources look identical to missing ones).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Rate limited. Back off and retry after the window in the `RateLimit-*` headers.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}},"tags":["Projects"],"x-token-scope":"write"}},"/projects/{id}/members/{userId}":{"delete":{"operationId":"removeProjectMember","summary":"Remove a member","description":"Project owner only.","parameters":[{"name":"id","in":"path","required":true,"description":"Project id.","schema":{"type":"string","format":"uuid"}},{"name":"userId","in":"path","required":true,"description":"User id.","schema":{"type":"string","format":"uuid"}}],"responses":{"204":{"description":"Removed."},"401":{"description":"Missing, invalid, expired or revoked credentials.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Authenticated, but not allowed: not a member, insufficient project role or the API token lacks the required scope.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"Not found, or not visible to the caller (tenancy is enforced: foreign resources look identical to missing ones).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Rate limited. Back off and retry after the window in the `RateLimit-*` headers.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}},"tags":["Projects"],"x-token-scope":"write"}},"/projects/{id}/invites":{"get":{"operationId":"listProjectInvites","summary":"List pending invites","description":"Project owner only. Unaccepted, unexpired invites, newest first.","parameters":[{"name":"id","in":"path","required":true,"description":"Project id.","schema":{"type":"string","format":"uuid"}}],"responses":{"200":{"description":"Invites.","content":{"application/json":{"schema":{"type":"object","properties":{"invites":{"type":"array","items":{"$ref":"#/components/schemas/Invite"}}},"required":["invites"]}}}},"401":{"description":"Missing, invalid, expired or revoked credentials.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Authenticated, but not allowed: not a member, insufficient project role or the API token lacks the required scope.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"Not found, or not visible to the caller (tenancy is enforced: foreign resources look identical to missing ones).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Rate limited. Back off and retry after the window in the `RateLimit-*` headers.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}},"tags":["Projects"],"x-token-scope":"read"},"post":{"operationId":"createProjectInvite","summary":"Invite someone by email","description":"Project owner only. Emails a 7-day invite link; any earlier pending invite for the same address is replaced. The raw accept token is also returned. On instances with billing enforcement enabled, inviting someone new past the workspace plan's people limit returns 403; re-inviting an existing person always succeeds.","parameters":[{"name":"id","in":"path","required":true,"description":"Project id.","schema":{"type":"string","format":"uuid"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"email":{"type":"string","format":"email"},"role":{"type":"string","enum":["editor","viewer"],"description":"Ownership cannot be granted through the API."}},"required":["email","role"]}}}},"responses":{"201":{"description":"Sent.","content":{"application/json":{"schema":{"type":"object","properties":{"message":{"type":"string"},"token":{"type":"string","description":"Accept token: the same one embedded in the emailed link."}},"required":["message","token"]}}}},"400":{"description":"Validation failed: the body or query did not match the schema, or a business rule was violated. `error` says which.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Missing, invalid, expired or revoked credentials.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Authenticated, but not allowed: not a member, insufficient project role or the API token lacks the required scope.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"Not found, or not visible to the caller (tenancy is enforced: foreign resources look identical to missing ones).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Rate limited. Back off and retry after the window in the `RateLimit-*` headers.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}},"tags":["Projects"],"x-token-scope":"write"}},"/projects/{id}/invites/{inviteId}":{"delete":{"operationId":"cancelProjectInvite","summary":"Cancel a pending invite","description":"Project owner only. Idempotent.","parameters":[{"name":"id","in":"path","required":true,"description":"Project id.","schema":{"type":"string","format":"uuid"}},{"name":"inviteId","in":"path","required":true,"description":"Invite id.","schema":{"type":"string","format":"uuid"}}],"responses":{"204":{"description":"Cancelled."},"401":{"description":"Missing, invalid, expired or revoked credentials.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Authenticated, but not allowed: not a member, insufficient project role or the API token lacks the required scope.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"Not found, or not visible to the caller (tenancy is enforced: foreign resources look identical to missing ones).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Rate limited. Back off and retry after the window in the `RateLimit-*` headers.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}},"tags":["Projects"],"x-token-scope":"write"}},"/projects/{projectId}/sections":{"get":{"operationId":"listSections","summary":"List a project's sections","description":"Sections ordered by `position`. Any project member may list. A section named like \"Done\", \"In progress\", \"Blocked\" or \"To do\" implies a task status when tasks are moved into it.","parameters":[{"name":"projectId","in":"path","required":true,"description":"Project id.","schema":{"type":"string","format":"uuid"}}],"responses":{"200":{"description":"Sections.","content":{"application/json":{"schema":{"type":"object","properties":{"sections":{"type":"array","items":{"$ref":"#/components/schemas/Section"}}},"required":["sections"]}}}},"401":{"description":"Missing, invalid, expired or revoked credentials.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Authenticated, but not allowed: not a member, insufficient project role or the API token lacks the required scope.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Rate limited. Back off and retry after the window in the `RateLimit-*` headers.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}},"tags":["Sections"],"x-token-scope":"read"},"post":{"operationId":"createSection","summary":"Create a section","description":"Project members with a role other than `viewer`.","parameters":[{"name":"projectId","in":"path","required":true,"description":"Project id.","schema":{"type":"string","format":"uuid"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"name":{"type":"string","minLength":1,"maxLength":255},"position":{"type":"number","description":"Sort key; defaults to (max + 1)."}},"required":["name"]}}}},"responses":{"201":{"description":"Created.","content":{"application/json":{"schema":{"type":"object","properties":{"section":{"$ref":"#/components/schemas/Section"}},"required":["section"]}}}},"400":{"description":"Validation failed: the body or query did not match the schema, or a business rule was violated. `error` says which.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Missing, invalid, expired or revoked credentials.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Authenticated, but not allowed: not a member, insufficient project role or the API token lacks the required scope.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Rate limited. Back off and retry after the window in the `RateLimit-*` headers.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}},"tags":["Sections"],"x-token-scope":"write"}},"/sections/{id}":{"patch":{"operationId":"updateSection","summary":"Rename, reorder or collapse a section","description":"Renaming a section does not retro-actively change the status of tasks already in it.","parameters":[{"name":"id","in":"path","required":true,"description":"Section id.","schema":{"type":"string","format":"uuid"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"name":{"type":"string","minLength":1,"maxLength":255},"position":{"type":"number","description":"Sort key; defaults to (max + 1)."},"collapsed":{"type":"boolean"}}}}}},"responses":{"200":{"description":"Updated.","content":{"application/json":{"schema":{"type":"object","properties":{"section":{"$ref":"#/components/schemas/Section"}},"required":["section"]}}}},"400":{"description":"Validation failed: the body or query did not match the schema, or a business rule was violated. `error` says which.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Missing, invalid, expired or revoked credentials.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Authenticated, but not allowed: not a member, insufficient project role or the API token lacks the required scope.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"Not found, or not visible to the caller (tenancy is enforced: foreign resources look identical to missing ones).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Rate limited. Back off and retry after the window in the `RateLimit-*` headers.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}},"tags":["Sections"],"x-token-scope":"write"},"delete":{"operationId":"deleteSection","summary":"Delete a section","parameters":[{"name":"id","in":"path","required":true,"description":"Section id.","schema":{"type":"string","format":"uuid"}},{"name":"move_tasks_to","in":"query","required":false,"description":"Move the section's tasks to this section (same project) instead of leaving them sectionless.","schema":{"type":"string","format":"uuid"}}],"responses":{"204":{"description":"Deleted."},"400":{"description":"Validation failed: the body or query did not match the schema, or a business rule was violated. `error` says which.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Missing, invalid, expired or revoked credentials.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Authenticated, but not allowed: not a member, insufficient project role or the API token lacks the required scope.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"Not found, or not visible to the caller (tenancy is enforced: foreign resources look identical to missing ones).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Rate limited. Back off and retry after the window in the `RateLimit-*` headers.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}},"tags":["Sections"],"x-token-scope":"write"}},"/tasks":{"get":{"operationId":"listTasks","summary":"List tasks","description":"Tasks in projects the caller belongs to, ordered by `position`. Pass `exclude_completed=true` and read `total` to answer \"how many open\" questions in one call.","parameters":[{"name":"project_id","in":"query","required":false,"description":"Only tasks in this project.","schema":{"type":"string","format":"uuid"}},{"name":"workspace_id","in":"query","required":false,"description":"Only tasks in projects of this workspace.","schema":{"type":"string","format":"uuid"}},{"name":"assignee_id","in":"query","required":false,"description":"Only tasks assigned to this user.","schema":{"type":"string","format":"uuid"}},{"name":"status","in":"query","required":false,"description":"Exact status.","schema":{"type":"string","enum":["not_started","in_progress","blocked","completed"]}},{"name":"priority","in":"query","required":false,"description":"Exact priority.","schema":{"type":"string","enum":["none","low","medium","high","urgent"]}},{"name":"due_before","in":"query","required":false,"description":"Due on or before this date (inclusive).","schema":{"type":"string","format":"date"}},{"name":"due_after","in":"query","required":false,"description":"Due on or after this date (inclusive).","schema":{"type":"string","format":"date"}},{"name":"include_subtasks","in":"query","required":false,"description":"Default `false`: only top-level tasks are returned.","schema":{"type":"string","enum":["true","false"]}},{"name":"exclude_completed","in":"query","required":false,"description":"Ignored when `status` is given. `true` = everything except `completed`.","schema":{"type":"string","enum":["true","false"]}},{"name":"search","in":"query","required":false,"description":"Case-insensitive substring match on the task title.","schema":{"type":"string","minLength":1,"maxLength":200}},{"name":"page","in":"query","required":false,"description":"1-based page number.","schema":{"type":"integer","minimum":1,"default":1}},{"name":"per_page","in":"query","required":false,"description":"Page size. Values above 1000 are clamped to 1000.","schema":{"type":"integer","minimum":1,"maximum":1000,"default":50}}],"responses":{"200":{"description":"A page of tasks.","content":{"application/json":{"schema":{"type":"object","properties":{"tasks":{"type":"array","items":{"$ref":"#/components/schemas/Task"}},"total":{"type":"integer"},"page":{"type":"integer"},"per_page":{"type":"integer"}},"required":["tasks","total","page","per_page"]}}}},"400":{"description":"Validation failed: the body or query did not match the schema, or a business rule was violated. `error` says which.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Missing, invalid, expired or revoked credentials.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Authenticated, but not allowed: not a member, insufficient project role or the API token lacks the required scope.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Rate limited. Back off and retry after the window in the `RateLimit-*` headers.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}},"tags":["Tasks"],"x-token-scope":"read"},"post":{"operationId":"createTask","summary":"Create a task","description":"Caller must be a project member with a role other than `viewer`. The task is appended to its section, the creator and assignee auto-watch it and the assignee is notified. Subtasks nest up to 5 levels and inherit `blocked` if the parent has incomplete dependencies.","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"title":{"type":"string","minLength":1,"maxLength":255},"description":{"type":"string"},"section_id":{"type":"string","format":"uuid","description":"Must belong to the task's project."},"assignee_id":{"type":"string","format":"uuid","description":"Must be a member of the project."},"start_date":{"type":"string","description":"Any value `new Date()` accepts; use `YYYY-MM-DD`."},"due_date":{"type":"string","description":"Any value `new Date()` accepts; use `YYYY-MM-DD`."},"due_date_type":{"$ref":"#/components/schemas/DueDateType"},"relative_offset_days":{"type":"integer"},"relative_offset_from_task_id":{"type":"string","format":"uuid","description":"Anchor task for relative due dates; must be in the same project."},"priority":{"type":"string","enum":["none","low","medium","high","urgent"]},"status":{"type":"string","enum":["not_started","in_progress","blocked","completed"]},"tag_ids":{"type":"array","items":{"type":"string","format":"uuid"},"description":"On PATCH this REPLACES the whole tag set. Duplicate ids are deduplicated; every id must be a tag of the task's workspace, else 400."},"category":{"type":"string","maxLength":80},"recurrence":{"type":"string","enum":["daily","weekly","fortnightly","monthly"],"description":"Repeat rule. Completing a recurring task spawns the next occurrence exactly once: due (and start, keeping the same lead time) advanced by the rule, placed in the first not-done section, keeping assignee, priority, tags and subtasks (reset). Send null on PATCH to stop repeating."},"primary_project_id":{"type":"string","format":"uuid"},"parent_task_id":{"type":"string","format":"uuid","description":"Create as a subtask of this task (same project)."}},"required":["title","primary_project_id"]}}}},"responses":{"201":{"description":"Created.","content":{"application/json":{"schema":{"type":"object","properties":{"task":{"$ref":"#/components/schemas/Task"}},"required":["task"]}}}},"400":{"description":"Validation failed: the body or query did not match the schema, or a business rule was violated. `error` says which.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Missing, invalid, expired or revoked credentials.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Authenticated, but not allowed: not a member, insufficient project role or the API token lacks the required scope.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"Not found, or not visible to the caller (tenancy is enforced: foreign resources look identical to missing ones).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Rate limited. Back off and retry after the window in the `RateLimit-*` headers.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}},"tags":["Tasks"],"x-token-scope":"write"}},"/tasks/{id}":{"get":{"operationId":"getTask","summary":"Get a task with subtasks, dependencies, comments, attachments, activity and watchers","parameters":[{"name":"id","in":"path","required":true,"description":"Task id.","schema":{"type":"string","format":"uuid"}}],"responses":{"200":{"description":"The task.","content":{"application/json":{"schema":{"type":"object","properties":{"task":{"$ref":"#/components/schemas/TaskDetail"}},"required":["task"]}}}},"401":{"description":"Missing, invalid, expired or revoked credentials.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Authenticated, but not allowed: not a member, insufficient project role or the API token lacks the required scope.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"Not found, or not visible to the caller (tenancy is enforced: foreign resources look identical to missing ones).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Rate limited. Back off and retry after the window in the `RateLimit-*` headers.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}},"tags":["Tasks"],"x-token-scope":"read"},"patch":{"operationId":"updateTask","summary":"Update a task","description":"Partial update. Send `null` to clear a nullable field. Moving a task into a section whose name implies a status (e.g. \"Done\", \"In progress\", \"Blocked\") applies that status; completing a task stamps `completed_at`, moves it to the project's done column (the section named \"Done\" if present, else the first section whose name implies completed, e.g. \"Finished\" or \"Shipped\"), unblocks dependents and notifies watchers. Moving a completed task out of the done column reopens it like POST /tasks/{id}/reopen: status becomes `blocked` if any dependency is incomplete, else the section-implied status; an explicit `status` in the same request overrides this.","parameters":[{"name":"id","in":"path","required":true,"description":"Task id.","schema":{"type":"string","format":"uuid"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"title":{"type":"string","minLength":1,"maxLength":255},"description":{"type":["string","null"]},"assignee_id":{"type":["string","null"],"format":"uuid","description":"Must be a member of the project."},"section_id":{"type":["string","null"],"format":"uuid","description":"Must belong to the task's project."},"start_date":{"type":["string","null"],"description":"Any value `new Date()` accepts; use `YYYY-MM-DD`."},"due_date":{"type":["string","null"],"description":"Any value `new Date()` accepts; use `YYYY-MM-DD`."},"due_date_type":{"$ref":"#/components/schemas/DueDateType"},"relative_offset_days":{"type":["integer","null"]},"relative_offset_from_task_id":{"type":["string","null"],"format":"uuid","description":"Anchor task for relative due dates; must be in the same project."},"priority":{"type":"string","enum":["none","low","medium","high","urgent"]},"status":{"type":"string","enum":["not_started","in_progress","blocked","completed"]},"position":{"type":"number","description":"Reorder within the section."},"tag_ids":{"type":"array","items":{"type":"string","format":"uuid"},"description":"On PATCH this REPLACES the whole tag set. Duplicate ids are deduplicated; every id must be a tag of the task's workspace, else 400."},"category":{"type":["string","null"],"maxLength":80},"recurrence":{"type":["string","null"],"enum":["daily","weekly","fortnightly","monthly"],"description":"Repeat rule. Completing a recurring task spawns the next occurrence exactly once: due (and start, keeping the same lead time) advanced by the rule, placed in the first not-done section, keeping assignee, priority, tags and subtasks (reset). Send null on PATCH to stop repeating."}}}}}},"responses":{"200":{"description":"Updated.","content":{"application/json":{"schema":{"type":"object","properties":{"task":{"$ref":"#/components/schemas/Task"}},"required":["task"]}}}},"400":{"description":"Validation failed: the body or query did not match the schema, or a business rule was violated. `error` says which.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Missing, invalid, expired or revoked credentials.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Authenticated, but not allowed: not a member, insufficient project role or the API token lacks the required scope.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"Not found, or not visible to the caller (tenancy is enforced: foreign resources look identical to missing ones).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Rate limited. Back off and retry after the window in the `RateLimit-*` headers.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}},"tags":["Tasks"],"x-token-scope":"write"},"delete":{"operationId":"deleteTask","summary":"Delete a task","description":"Allowed for the creator, instance admins or project members with a role other than `viewer`.","parameters":[{"name":"id","in":"path","required":true,"description":"Task id.","schema":{"type":"string","format":"uuid"}},{"name":"delete_subtasks","in":"query","required":false,"description":"`true` deletes subtasks too; otherwise they are promoted to top-level tasks.","schema":{"type":"string","enum":["true","false"]}}],"responses":{"204":{"description":"Deleted."},"401":{"description":"Missing, invalid, expired or revoked credentials.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Authenticated, but not allowed: not a member, insufficient project role or the API token lacks the required scope.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"Not found, or not visible to the caller (tenancy is enforced: foreign resources look identical to missing ones).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Rate limited. Back off and retry after the window in the `RateLimit-*` headers.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}},"tags":["Tasks"],"x-token-scope":"write"}},"/tasks/{id}/complete":{"post":{"operationId":"completeTask","summary":"Complete a task","description":"Marks the task completed, moves it to the project's done column (the section named \"Done\" if present, else the first section whose name implies completed, e.g. \"Finished\" or \"Shipped\"), unblocks dependents whose remaining dependencies are all complete and notifies watchers. When the task carries a recurrence rule and has not spawned yet, the next occurrence is created and returned as `next_occurrence` (null otherwise).","parameters":[{"name":"id","in":"path","required":true,"description":"Task id.","schema":{"type":"string","format":"uuid"}}],"responses":{"200":{"description":"Completed.","content":{"application/json":{"schema":{"type":"object","properties":{"task":{"$ref":"#/components/schemas/Task"},"unblocked_tasks":{"type":"array","items":{"type":"object","properties":{"id":{"type":"string","format":"uuid"},"title":{"type":"string"}},"required":["id","title"]}},"next_occurrence":{"anyOf":[{"$ref":"#/components/schemas/Task"},{"type":"null"}]}},"required":["task","unblocked_tasks","next_occurrence"]}}}},"400":{"description":"Validation failed: the body or query did not match the schema, or a business rule was violated. `error` says which.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Missing, invalid, expired or revoked credentials.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Authenticated, but not allowed: not a member, insufficient project role or the API token lacks the required scope.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"Not found, or not visible to the caller (tenancy is enforced: foreign resources look identical to missing ones).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Rate limited. Back off and retry after the window in the `RateLimit-*` headers.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}},"tags":["Tasks"],"x-token-scope":"write"}},"/tasks/{id}/reopen":{"post":{"operationId":"reopenTask","summary":"Reopen a completed task","description":"Status becomes `blocked` if any dependency is incomplete, else `in_progress`; the task leaves the \"Done\" section.","parameters":[{"name":"id","in":"path","required":true,"description":"Task id.","schema":{"type":"string","format":"uuid"}}],"responses":{"200":{"description":"Reopened.","content":{"application/json":{"schema":{"type":"object","properties":{"task":{"$ref":"#/components/schemas/Task"}},"required":["task"]}}}},"400":{"description":"Validation failed: the body or query did not match the schema, or a business rule was violated. `error` says which.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Missing, invalid, expired or revoked credentials.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Authenticated, but not allowed: not a member, insufficient project role or the API token lacks the required scope.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"Not found, or not visible to the caller (tenancy is enforced: foreign resources look identical to missing ones).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Rate limited. Back off and retry after the window in the `RateLimit-*` headers.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}},"tags":["Tasks"],"x-token-scope":"write"}},"/tasks/{id}/dependencies":{"post":{"operationId":"addTaskDependency","summary":"Add a dependency (this task depends on another)","description":"Both tasks must be in the same workspace; cycles are rejected. If the depended-on task is incomplete, this task becomes `blocked`.","parameters":[{"name":"id","in":"path","required":true,"description":"Task id.","schema":{"type":"string","format":"uuid"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"depends_on_task_id":{"type":"string","format":"uuid"},"dependency_type":{"$ref":"#/components/schemas/DependencyType"},"lag_days":{"type":"integer","default":0}},"required":["depends_on_task_id"]}}}},"responses":{"201":{"description":"Created.","content":{"application/json":{"schema":{"type":"object","properties":{"dependency":{"type":"object","properties":{"id":{"type":"string","format":"uuid"},"dependentTaskId":{"type":"string","format":"uuid"},"dependsOnTaskId":{"type":"string","format":"uuid"},"dependencyType":{"$ref":"#/components/schemas/DependencyType"},"lagDays":{"type":"integer"},"createdAt":{"type":"string","format":"date-time"}},"required":["id","dependentTaskId","dependsOnTaskId"],"description":"Raw dependency row (camelCase)."}},"required":["dependency"]}}}},"400":{"description":"Validation failed: the body or query did not match the schema, or a business rule was violated. `error` says which.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Missing, invalid, expired or revoked credentials.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Authenticated, but not allowed: not a member, insufficient project role or the API token lacks the required scope.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"Not found, or not visible to the caller (tenancy is enforced: foreign resources look identical to missing ones).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Rate limited. Back off and retry after the window in the `RateLimit-*` headers.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}},"tags":["Tasks"],"x-token-scope":"write"}},"/tasks/{id}/dependencies/{dependencyId}":{"delete":{"operationId":"removeTaskDependency","summary":"Remove a dependency","description":"If the task has no remaining incomplete dependencies and was `blocked`, it becomes `not_started`.","parameters":[{"name":"id","in":"path","required":true,"description":"Task id.","schema":{"type":"string","format":"uuid"}},{"name":"dependencyId","in":"path","required":true,"description":"Dependency row id (`dep_id` in GET /tasks/{id}).","schema":{"type":"string","format":"uuid"}}],"responses":{"204":{"description":"Removed."},"401":{"description":"Missing, invalid, expired or revoked credentials.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Authenticated, but not allowed: not a member, insufficient project role or the API token lacks the required scope.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"Not found, or not visible to the caller (tenancy is enforced: foreign resources look identical to missing ones).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Rate limited. Back off and retry after the window in the `RateLimit-*` headers.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}},"tags":["Tasks"],"x-token-scope":"write"}},"/tasks/{id}/calculate-date-shifts":{"post":{"operationId":"previewDateShifts","summary":"Preview a cascading due-date change","description":"Dry run: which downstream dependents would move if this task's due date changed. Nothing is written. (It is a POST, so a `tf_` token needs the `write` scope.)","parameters":[{"name":"id","in":"path","required":true,"description":"Task id.","schema":{"type":"string","format":"uuid"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"new_due_date":{"type":"string","format":"date","description":"Calendar date, YYYY-MM-DD"},"shift_mode":{"type":"string","enum":["maintain_buffer","consume_buffer","none"],"description":"`maintain_buffer` shifts every dependent by the same delta; `consume_buffer` only pushes dependents that would otherwise start too early; `none` returns an empty proposal."}},"required":["new_due_date","shift_mode"]}}}},"responses":{"200":{"description":"Proposal.","content":{"application/json":{"schema":{"type":"object","properties":{"proposed_changes":{"type":"array","items":{"$ref":"#/components/schemas/DateShiftProposal"}},"total_affected":{"type":"integer"}},"required":["proposed_changes","total_affected"]}}}},"400":{"description":"Validation failed: the body or query did not match the schema, or a business rule was violated. `error` says which.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Missing, invalid, expired or revoked credentials.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Authenticated, but not allowed: not a member, insufficient project role or the API token lacks the required scope.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"Not found, or not visible to the caller (tenancy is enforced: foreign resources look identical to missing ones).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Rate limited. Back off and retry after the window in the `RateLimit-*` headers.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}},"tags":["Tasks"],"x-token-scope":"write"}},"/tasks/{id}/apply-date-shifts":{"post":{"operationId":"applyDateShifts","summary":"Apply a cascading due-date change","description":"Sets this task's due date and applies the listed changes (normally the output of the preview) in one transaction. The caller must be a member of every affected task's project AND have edit permission on every affected task (the same rule as PATCH /tasks/{id}); a viewer role is refused with 403.","parameters":[{"name":"id","in":"path","required":true,"description":"Task id.","schema":{"type":"string","format":"uuid"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"new_due_date":{"type":"string","format":"date","description":"Calendar date, YYYY-MM-DD"},"changes":{"type":"array","items":{"type":"object","properties":{"task_id":{"type":"string","format":"uuid"},"new_due_date":{"type":"string","format":"date","description":"Calendar date, YYYY-MM-DD"}},"required":["task_id","new_due_date"]},"maxItems":500}},"required":["new_due_date","changes"]}}}},"responses":{"200":{"description":"Applied. Contains the cascaded tasks (not the anchor task).","content":{"application/json":{"schema":{"type":"object","properties":{"updated_tasks":{"type":"array","items":{"$ref":"#/components/schemas/Task"}}},"required":["updated_tasks"]}}}},"400":{"description":"Validation failed: the body or query did not match the schema, or a business rule was violated. `error` says which.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Missing, invalid, expired or revoked credentials.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Authenticated, but not allowed: not a member, insufficient project role or the API token lacks the required scope.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"Not found, or not visible to the caller (tenancy is enforced: foreign resources look identical to missing ones).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Rate limited. Back off and retry after the window in the `RateLimit-*` headers.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}},"tags":["Tasks"],"x-token-scope":"write"}},"/tasks/{taskId}/comments":{"post":{"operationId":"createComment","summary":"Add a comment","description":"Any project member (including viewers) may comment. Mentioned users who are not project members are silently dropped; valid mentions are notified immediately, other watchers via digest. The commenter auto-watches the task.","parameters":[{"name":"taskId","in":"path","required":true,"description":"Task id.","schema":{"type":"string","format":"uuid"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"content":{"type":"string","minLength":1},"mentions":{"type":"array","items":{"type":"string","format":"uuid"},"description":"User ids to @mention."}},"required":["content"]}}}},"responses":{"201":{"description":"Created.","content":{"application/json":{"schema":{"type":"object","properties":{"comment":{"$ref":"#/components/schemas/Comment"}},"required":["comment"]}}}},"400":{"description":"Validation failed: the body or query did not match the schema, or a business rule was violated. `error` says which.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Missing, invalid, expired or revoked credentials.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Authenticated, but not allowed: not a member, insufficient project role or the API token lacks the required scope.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"Not found, or not visible to the caller (tenancy is enforced: foreign resources look identical to missing ones).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Rate limited. Back off and retry after the window in the `RateLimit-*` headers.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}},"tags":["Comments"],"x-token-scope":"write"}},"/tasks/{taskId}/comments/{id}":{"patch":{"operationId":"updateComment","summary":"Edit a comment","description":"Author only, within 5 minutes of posting. Sets `edited: true`.","parameters":[{"name":"taskId","in":"path","required":true,"description":"Task id.","schema":{"type":"string","format":"uuid"}},{"name":"id","in":"path","required":true,"description":"Comment id.","schema":{"type":"string","format":"uuid"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"content":{"type":"string","minLength":1}},"required":["content"]}}}},"responses":{"200":{"description":"Updated.","content":{"application/json":{"schema":{"type":"object","properties":{"comment":{"$ref":"#/components/schemas/Comment"}},"required":["comment"]}}}},"400":{"description":"Validation failed: the body or query did not match the schema, or a business rule was violated. `error` says which.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Missing, invalid, expired or revoked credentials.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Authenticated, but not allowed: not a member, insufficient project role or the API token lacks the required scope.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"Not found, or not visible to the caller (tenancy is enforced: foreign resources look identical to missing ones).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Rate limited. Back off and retry after the window in the `RateLimit-*` headers.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}},"tags":["Comments"],"x-token-scope":"write"},"delete":{"operationId":"deleteComment","summary":"Delete a comment","description":"Author or instance admin.","parameters":[{"name":"taskId","in":"path","required":true,"description":"Task id.","schema":{"type":"string","format":"uuid"}},{"name":"id","in":"path","required":true,"description":"Comment id.","schema":{"type":"string","format":"uuid"}}],"responses":{"204":{"description":"Deleted."},"401":{"description":"Missing, invalid, expired or revoked credentials.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Authenticated, but not allowed: not a member, insufficient project role or the API token lacks the required scope.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"Not found, or not visible to the caller (tenancy is enforced: foreign resources look identical to missing ones).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Rate limited. Back off and retry after the window in the `RateLimit-*` headers.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}},"tags":["Comments"],"x-token-scope":"write"}},"/tasks/{taskId}/watchers":{"get":{"operationId":"listWatchers","summary":"List watchers","parameters":[{"name":"taskId","in":"path","required":true,"description":"Task id.","schema":{"type":"string","format":"uuid"}}],"responses":{"200":{"description":"Watchers, oldest first.","content":{"application/json":{"schema":{"type":"object","properties":{"watchers":{"type":"array","items":{"allOf":[{"$ref":"#/components/schemas/Watcher"},{"type":"object","properties":{"added_at":{"type":"string","format":"date-time"}},"required":["added_at"]}]}}},"required":["watchers"]}}}},"401":{"description":"Missing, invalid, expired or revoked credentials.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Authenticated, but not allowed: not a member, insufficient project role or the API token lacks the required scope.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"Not found, or not visible to the caller (tenancy is enforced: foreign resources look identical to missing ones).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Rate limited. Back off and retry after the window in the `RateLimit-*` headers.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}},"tags":["Watchers"],"x-token-scope":"read"},"post":{"operationId":"addWatcher","summary":"Add a watcher","description":"Idempotent. Viewers may only add themselves; other roles may add any project member.","parameters":[{"name":"taskId","in":"path","required":true,"description":"Task id.","schema":{"type":"string","format":"uuid"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"user_id":{"type":"string","format":"uuid"}},"required":["user_id"]}}}},"responses":{"201":{"description":"Added (or already watching).","content":{"application/json":{"schema":{"type":"object","properties":{"watcher":{"$ref":"#/components/schemas/Watcher"}},"required":["watcher"]}}}},"400":{"description":"Validation failed: the body or query did not match the schema, or a business rule was violated. `error` says which.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Missing, invalid, expired or revoked credentials.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Authenticated, but not allowed: not a member, insufficient project role or the API token lacks the required scope.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"Not found, or not visible to the caller (tenancy is enforced: foreign resources look identical to missing ones).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Rate limited. Back off and retry after the window in the `RateLimit-*` headers.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}},"tags":["Watchers"],"x-token-scope":"write"}},"/tasks/{taskId}/watchers/{userId}":{"delete":{"operationId":"removeWatcher","summary":"Remove a watcher","description":"Anyone may remove themselves; only project owners may remove others. Removing a non-watcher is a silent 204.","parameters":[{"name":"taskId","in":"path","required":true,"description":"Task id.","schema":{"type":"string","format":"uuid"}},{"name":"userId","in":"path","required":true,"description":"User id.","schema":{"type":"string","format":"uuid"}}],"responses":{"204":{"description":"Removed."},"401":{"description":"Missing, invalid, expired or revoked credentials.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Authenticated, but not allowed: not a member, insufficient project role or the API token lacks the required scope.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"Not found, or not visible to the caller (tenancy is enforced: foreign resources look identical to missing ones).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Rate limited. Back off and retry after the window in the `RateLimit-*` headers.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}},"tags":["Watchers"],"x-token-scope":"write"}},"/tasks/{taskId}/attachments":{"get":{"operationId":"listAttachments","summary":"List attachments (metadata)","parameters":[{"name":"taskId","in":"path","required":true,"description":"Task id.","schema":{"type":"string","format":"uuid"}}],"responses":{"200":{"description":"Attachments, oldest first.","content":{"application/json":{"schema":{"type":"object","properties":{"attachments":{"type":"array","items":{"$ref":"#/components/schemas/AttachmentSummary"}}},"required":["attachments"]}}}},"401":{"description":"Missing, invalid, expired or revoked credentials.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Authenticated, but not allowed: not a member, insufficient project role or the API token lacks the required scope.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"Not found, or not visible to the caller (tenancy is enforced: foreign resources look identical to missing ones).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Rate limited. Back off and retry after the window in the `RateLimit-*` headers.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}},"tags":["Attachments"],"x-token-scope":"read"},"post":{"operationId":"uploadAttachment","summary":"Upload a file","description":"multipart/form-data with a single `file` field, max 25 MB. Allowed types: JPEG, PNG, GIF, WebP, PDF, plain text, CSV, ZIP, Word, Excel, PowerPoint (SVG is refused). The project owner is notified.","parameters":[{"name":"taskId","in":"path","required":true,"description":"Task id.","schema":{"type":"string","format":"uuid"}}],"requestBody":{"required":true,"content":{"multipart/form-data":{"schema":{"type":"object","properties":{"file":{"type":"string","format":"binary"}},"required":["file"]}}}},"responses":{"201":{"description":"Stored.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Attachment"}}}},"400":{"description":"Validation failed: the body or query did not match the schema, or a business rule was violated. `error` says which.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Missing, invalid, expired or revoked credentials.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Authenticated, but not allowed: not a member, insufficient project role or the API token lacks the required scope.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"Not found, or not visible to the caller (tenancy is enforced: foreign resources look identical to missing ones).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Rate limited. Back off and retry after the window in the `RateLimit-*` headers.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}},"tags":["Attachments"],"x-token-scope":"write"}},"/tasks/{taskId}/attachments/{attachmentId}/content":{"get":{"operationId":"readAttachmentContent","summary":"Read an attachment as text/base64 (agent-friendly)","description":"Text and CSV are returned verbatim, PDFs are text-extracted, images are base64; other types are reported as unsupported.","parameters":[{"name":"taskId","in":"path","required":true,"description":"Task id.","schema":{"type":"string","format":"uuid"}},{"name":"attachmentId","in":"path","required":true,"description":"Attachment id.","schema":{"type":"string","format":"uuid"}}],"responses":{"200":{"description":"Extracted content.","content":{"application/json":{"schema":{"type":"object","properties":{"id":{"type":"string","format":"uuid"},"filename":{"type":"string"},"mime_type":{"type":"string"},"file_size":{"type":"integer","description":"Bytes."},"kind":{"type":"string","description":"`text`, `image`, `unsupported` …"},"content":{"type":["string","null"]},"truncated":{"type":"boolean"}},"required":["id","filename","mime_type"],"additionalProperties":true}}}},"401":{"description":"Missing, invalid, expired or revoked credentials.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Authenticated, but not allowed: not a member, insufficient project role or the API token lacks the required scope.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"Not found, or not visible to the caller (tenancy is enforced: foreign resources look identical to missing ones).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"422":{"description":"The file exists but its content could not be extracted.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Rate limited. Back off and retry after the window in the `RateLimit-*` headers.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}},"tags":["Attachments"],"x-token-scope":"read"}},"/tasks/{taskId}/attachments/{attachmentId}":{"delete":{"operationId":"deleteAttachment","summary":"Delete an attachment","description":"Uploader or project owner.","parameters":[{"name":"taskId","in":"path","required":true,"description":"Task id.","schema":{"type":"string","format":"uuid"}},{"name":"attachmentId","in":"path","required":true,"description":"Attachment id.","schema":{"type":"string","format":"uuid"}}],"responses":{"204":{"description":"Deleted."},"401":{"description":"Missing, invalid, expired or revoked credentials.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Authenticated, but not allowed: not a member, insufficient project role or the API token lacks the required scope.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"Not found, or not visible to the caller (tenancy is enforced: foreign resources look identical to missing ones).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Rate limited. Back off and retry after the window in the `RateLimit-*` headers.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}},"tags":["Attachments"],"x-token-scope":"write"}},"/reports":{"post":{"operationId":"createReport","summary":"Render a report or export as a file","description":"Builds the file on the server from the tasks you can see, stores it for 30 days and returns where to fetch it. At most 5000 rows per file. Cells that would execute as spreadsheet formulas are neutralised in CSV. This is the same engine behind the assistant's `create_report` tool.","requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ReportSpec"}}}},"responses":{"201":{"description":"Rendered and stored.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ReportFile"}}}},"400":{"description":"Validation failed: the body or query did not match the schema, or a business rule was violated. `error` says which.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Missing, invalid, expired or revoked credentials.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Authenticated, but not allowed: not a member, insufficient project role or the API token lacks the required scope.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"Not found, or not visible to the caller (tenancy is enforced: foreign resources look identical to missing ones).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Rate limited. Back off and retry after the window in the `RateLimit-*` headers.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}},"tags":["Reports"],"x-token-scope":"write"},"get":{"operationId":"listReports","summary":"List your unexpired reports","description":"Newest first, at most 50. Only the requester can see or download a report: it was built with their visibility.","responses":{"200":{"description":"Your reports.","content":{"application/json":{"schema":{"type":"object","properties":{"reports":{"type":"array","items":{"$ref":"#/components/schemas/ReportFile"}}},"required":["reports"]}}}},"401":{"description":"Missing, invalid, expired or revoked credentials.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Authenticated, but not allowed: not a member, insufficient project role or the API token lacks the required scope.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Rate limited. Back off and retry after the window in the `RateLimit-*` headers.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}},"tags":["Reports"],"x-token-scope":"read"}},"/reports/{reportId}":{"get":{"operationId":"getReport","summary":"Report metadata","parameters":[{"name":"reportId","in":"path","required":true,"description":"Report id.","schema":{"type":"string","format":"uuid"}}],"responses":{"200":{"description":"The report.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ReportFile"}}}},"401":{"description":"Missing, invalid, expired or revoked credentials.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Authenticated, but not allowed: not a member, insufficient project role or the API token lacks the required scope.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"Not found, or not visible to the caller (tenancy is enforced: foreign resources look identical to missing ones).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Rate limited. Back off and retry after the window in the `RateLimit-*` headers.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}},"tags":["Reports"],"x-token-scope":"read"}},"/reports/{reportId}/download":{"get":{"operationId":"downloadReport","summary":"Download the file","description":"Streams the file with `Content-Disposition: attachment`. Authenticate with your bearer token, or pass the `t` token from `download_url` instead (no bearer needed until it expires).","parameters":[{"name":"reportId","in":"path","required":true,"description":"Report id.","schema":{"type":"string","format":"uuid"}},{"name":"t","in":"query","required":false,"description":"Signed link token from download_url; valid 24 hours.","schema":{"type":"string"}}],"responses":{"200":{"description":"The file; `Content-Type` is the report format.","content":{"application/octet-stream":{"schema":{"type":"string","format":"binary"}}}},"401":{"description":"Missing, invalid, expired or revoked credentials.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Authenticated, but not allowed: not a member, insufficient project role or the API token lacks the required scope.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"Not found, or not visible to the caller (tenancy is enforced: foreign resources look identical to missing ones).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Rate limited. Back off and retry after the window in the `RateLimit-*` headers.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}},"tags":["Reports"],"x-token-scope":"read"}},"/workspaces/{workspaceId}/tags":{"get":{"operationId":"listTags","summary":"List a workspace's tags","parameters":[{"name":"workspaceId","in":"path","required":true,"description":"Workspace id.","schema":{"type":"string","format":"uuid"}}],"responses":{"200":{"description":"Tags, alphabetical.","content":{"application/json":{"schema":{"type":"object","properties":{"tags":{"type":"array","items":{"$ref":"#/components/schemas/WorkspaceTag"}}},"required":["tags"]}}}},"401":{"description":"Missing, invalid, expired or revoked credentials.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Authenticated, but not allowed: not a member, insufficient project role or the API token lacks the required scope.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Rate limited. Back off and retry after the window in the `RateLimit-*` headers.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}},"tags":["Tags & categories"],"x-token-scope":"read"},"post":{"operationId":"createTag","summary":"Create a tag","description":"Any user with workspace access. Names are unique per workspace.","parameters":[{"name":"workspaceId","in":"path","required":true,"description":"Workspace id.","schema":{"type":"string","format":"uuid"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"name":{"type":"string","minLength":1,"maxLength":100},"color":{"type":"string","pattern":"^#[0-9A-Fa-f]{6}$"}},"required":["name","color"]}}}},"responses":{"201":{"description":"Created.","content":{"application/json":{"schema":{"type":"object","properties":{"tag":{"$ref":"#/components/schemas/WorkspaceTag"}},"required":["tag"]}}}},"400":{"description":"Validation failed: the body or query did not match the schema, or a business rule was violated. `error` says which.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Missing, invalid, expired or revoked credentials.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Authenticated, but not allowed: not a member, insufficient project role or the API token lacks the required scope.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"409":{"description":"A tag with that name already exists in this workspace.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Rate limited. Back off and retry after the window in the `RateLimit-*` headers.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}},"tags":["Tags & categories"],"x-token-scope":"write"}},"/workspaces/{workspaceId}/tags/{id}":{"delete":{"operationId":"deleteTag","summary":"Delete a tag","description":"Removes it from every task.","parameters":[{"name":"workspaceId","in":"path","required":true,"description":"Workspace id.","schema":{"type":"string","format":"uuid"}},{"name":"id","in":"path","required":true,"description":"Tag id.","schema":{"type":"string","format":"uuid"}}],"responses":{"204":{"description":"Deleted."},"401":{"description":"Missing, invalid, expired or revoked credentials.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Authenticated, but not allowed: not a member, insufficient project role or the API token lacks the required scope.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"Not found, or not visible to the caller (tenancy is enforced: foreign resources look identical to missing ones).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Rate limited. Back off and retry after the window in the `RateLimit-*` headers.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}},"tags":["Tags & categories"],"x-token-scope":"write"}},"/workspaces/{workspaceId}/categories":{"get":{"operationId":"listCategories","summary":"List a workspace's categories","parameters":[{"name":"workspaceId","in":"path","required":true,"description":"Workspace id.","schema":{"type":"string","format":"uuid"}}],"responses":{"200":{"description":"Categories, alphabetical.","content":{"application/json":{"schema":{"type":"object","properties":{"categories":{"type":"array","items":{"$ref":"#/components/schemas/Category"}}},"required":["categories"]}}}},"401":{"description":"Missing, invalid, expired or revoked credentials.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Authenticated, but not allowed: not a member, insufficient project role or the API token lacks the required scope.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Rate limited. Back off and retry after the window in the `RateLimit-*` headers.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}},"tags":["Tags & categories"],"x-token-scope":"read"},"post":{"operationId":"createCategory","summary":"Create a category","description":"Idempotent by name: posting an existing name returns the existing row.","parameters":[{"name":"workspaceId","in":"path","required":true,"description":"Workspace id.","schema":{"type":"string","format":"uuid"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"name":{"type":"string","minLength":1,"maxLength":80}},"required":["name"]}}}},"responses":{"201":{"description":"Created (or already existed).","content":{"application/json":{"schema":{"type":"object","properties":{"category":{"$ref":"#/components/schemas/Category"}},"required":["category"]}}}},"400":{"description":"Validation failed: the body or query did not match the schema, or a business rule was violated. `error` says which.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Missing, invalid, expired or revoked credentials.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Authenticated, but not allowed: not a member, insufficient project role or the API token lacks the required scope.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Rate limited. Back off and retry after the window in the `RateLimit-*` headers.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}},"tags":["Tags & categories"],"x-token-scope":"write"}},"/workspaces/{workspaceId}/categories/{id}":{"delete":{"operationId":"deleteCategory","summary":"Delete a category","parameters":[{"name":"workspaceId","in":"path","required":true,"description":"Workspace id.","schema":{"type":"string","format":"uuid"}},{"name":"id","in":"path","required":true,"description":"Category id.","schema":{"type":"string","format":"uuid"}}],"responses":{"204":{"description":"Deleted."},"401":{"description":"Missing, invalid, expired or revoked credentials.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Authenticated, but not allowed: not a member, insufficient project role or the API token lacks the required scope.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"Not found, or not visible to the caller (tenancy is enforced: foreign resources look identical to missing ones).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Rate limited. Back off and retry after the window in the `RateLimit-*` headers.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}},"tags":["Tags & categories"],"x-token-scope":"write"}},"/events":{"get":{"operationId":"listEvents","summary":"Poll the change feed","description":"Every change in a workspace, oldest first, as a cursor-paginated stream: the building block for one-way sync and the same stream webhooks deliver. Visibility matches the rest of the API: only events in projects you belong to (workspace-level events are visible to any workspace member). Events are retained for 30 days.\n\n**Sync loop:** call without `since` once, store `next_cursor`, then poll with `since=<next_cursor>`; repeat immediately while `has_more` is true. Cursors are per instance, not per workspace, so gaps between consecutive cursors are normal.","parameters":[{"name":"workspace_id","in":"query","required":true,"description":"Workspace to read.","schema":{"type":"string","format":"uuid"}},{"name":"since","in":"query","required":false,"description":"The `next_cursor` from your previous call. Omit for the oldest retained events.","schema":{"type":"string","description":"Return only events after this cursor (exclusive)."}},{"name":"project_id","in":"query","required":false,"description":"Only events in this project.","schema":{"type":"string","format":"uuid"}},{"name":"types","in":"query","required":false,"description":"Only these event types. Unknown names are rejected (400).","schema":{"type":"string","description":"Comma-separated list, e.g. `task.created,task.completed`."}},{"name":"limit","in":"query","required":false,"description":"Page size. Values above 500 are clamped.","schema":{"type":"integer","minimum":1,"maximum":500,"default":100}}],"responses":{"200":{"description":"A page of events.","content":{"application/json":{"schema":{"type":"object","properties":{"events":{"type":"array","items":{"$ref":"#/components/schemas/Event"}},"next_cursor":{"type":["string","null"],"description":"Pass as `since` next time. Null only when there are no events at all yet."},"has_more":{"type":"boolean","description":"`true` means call again immediately with `since=next_cursor`."},"as_of":{"type":"string","format":"date-time","description":"Server time when the page was produced."}},"required":["events","next_cursor","has_more","as_of"]}}}},"400":{"description":"Validation failed: the body or query did not match the schema, or a business rule was violated. `error` says which.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Missing, invalid, expired or revoked credentials.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Authenticated, but not allowed: not a member, insufficient project role or the API token lacks the required scope.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Rate limited. Back off and retry after the window in the `RateLimit-*` headers.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}},"tags":["Events"],"x-token-scope":"read"}},"/workspaces/{workspaceId}/webhooks":{"get":{"operationId":"listWebhooks","summary":"List webhooks","description":"Workspace owner only. Secrets are never included.","parameters":[{"name":"workspaceId","in":"path","required":true,"description":"Workspace id.","schema":{"type":"string","format":"uuid"}}],"responses":{"200":{"description":"Webhooks.","content":{"application/json":{"schema":{"type":"object","properties":{"webhooks":{"type":"array","items":{"$ref":"#/components/schemas/Webhook"}}},"required":["webhooks"]}}}},"401":{"description":"Missing, invalid, expired or revoked credentials.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Authenticated, but not allowed: not a member, insufficient project role or the API token lacks the required scope.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Rate limited. Back off and retry after the window in the `RateLimit-*` headers.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}},"tags":["Webhooks"],"x-token-scope":"read"},"post":{"operationId":"createWebhook","summary":"Create a webhook","description":"Workspace owner only, max 10 per workspace. Delivery starts from the CURRENT stream position (future events only). The signing `secret` (`whsec_…`) is returned ONCE in this response and never again; store it.\n\n**Verifying deliveries.** Each POST carries `X-Webhook-Timestamp` (unix seconds) and `X-Webhook-Signature: v1=<hex>`, where the hex is HMAC-SHA256 of `\"<timestamp>.<raw body>\"` keyed with your secret. Recompute it over the RAW body, compare constant-time, and reject timestamps older than ~5 minutes. Delivery is at-least-once and ordered per webhook; dedupe on `event.id`. Respond 2xx quickly; anything else is retried with backoff (30s → 2m → 10m → 30m → hourly), and 20 consecutive failures disable the webhook.","parameters":[{"name":"workspaceId","in":"path","required":true,"description":"Workspace id.","schema":{"type":"string","format":"uuid"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"url":{"type":"string","format":"uri","maxLength":2000,"description":"Public https(s) endpoint. Private/internal addresses are refused, and redirects are not followed."},"description":{"type":"string","maxLength":200},"event_types":{"type":"array","items":{"$ref":"#/components/schemas/EventType"},"description":"Omit (or send []) to receive every type."}},"required":["url"]}}}},"responses":{"201":{"description":"Created. Includes the secret; this is the only time.","content":{"application/json":{"schema":{"type":"object","properties":{"webhook":{"allOf":[{"$ref":"#/components/schemas/Webhook"},{"type":"object","properties":{"secret":{"type":"string","description":"Signing secret, shown once."}},"required":["secret"]}]}},"required":["webhook"]}}}},"400":{"description":"Validation failed: the body or query did not match the schema, or a business rule was violated. `error` says which.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Missing, invalid, expired or revoked credentials.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Authenticated, but not allowed: not a member, insufficient project role or the API token lacks the required scope.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Rate limited. Back off and retry after the window in the `RateLimit-*` headers.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}},"tags":["Webhooks"],"x-token-scope":"write"}},"/workspaces/{workspaceId}/webhooks/{id}":{"patch":{"operationId":"updateWebhook","summary":"Update a webhook","description":"Workspace owner only. Setting `active: true` (or any edit while active) clears the failure/backoff state so delivery resumes immediately.","parameters":[{"name":"workspaceId","in":"path","required":true,"description":"Workspace id.","schema":{"type":"string","format":"uuid"}},{"name":"id","in":"path","required":true,"description":"Webhook id.","schema":{"type":"string","format":"uuid"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"url":{"type":"string","format":"uri","maxLength":2000,"description":"Public https(s) endpoint. Private/internal addresses are refused, and redirects are not followed."},"description":{"type":["string","null"],"maxLength":200},"event_types":{"type":"array","items":{"$ref":"#/components/schemas/EventType"},"description":"Omit (or send []) to receive every type."},"active":{"type":"boolean"}}}}}},"responses":{"200":{"description":"Updated.","content":{"application/json":{"schema":{"type":"object","properties":{"webhook":{"$ref":"#/components/schemas/Webhook"}},"required":["webhook"]}}}},"400":{"description":"Validation failed: the body or query did not match the schema, or a business rule was violated. `error` says which.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Missing, invalid, expired or revoked credentials.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Authenticated, but not allowed: not a member, insufficient project role or the API token lacks the required scope.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"Not found, or not visible to the caller (tenancy is enforced: foreign resources look identical to missing ones).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Rate limited. Back off and retry after the window in the `RateLimit-*` headers.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}},"tags":["Webhooks"],"x-token-scope":"write"},"delete":{"operationId":"deleteWebhook","summary":"Delete a webhook","description":"Workspace owner only. Also deletes its delivery log.","parameters":[{"name":"workspaceId","in":"path","required":true,"description":"Workspace id.","schema":{"type":"string","format":"uuid"}},{"name":"id","in":"path","required":true,"description":"Webhook id.","schema":{"type":"string","format":"uuid"}}],"responses":{"204":{"description":"Deleted."},"401":{"description":"Missing, invalid, expired or revoked credentials.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Authenticated, but not allowed: not a member, insufficient project role or the API token lacks the required scope.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"Not found, or not visible to the caller (tenancy is enforced: foreign resources look identical to missing ones).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Rate limited. Back off and retry after the window in the `RateLimit-*` headers.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}},"tags":["Webhooks"],"x-token-scope":"write"}},"/workspaces/{workspaceId}/webhooks/{id}/rotate-secret":{"post":{"operationId":"rotateWebhookSecret","summary":"Rotate the signing secret","description":"Workspace owner only. Deliveries sign with the new secret immediately. The signing `secret` (`whsec_…`) is returned ONCE in this response and never again; store it.","parameters":[{"name":"workspaceId","in":"path","required":true,"description":"Workspace id.","schema":{"type":"string","format":"uuid"}},{"name":"id","in":"path","required":true,"description":"Webhook id.","schema":{"type":"string","format":"uuid"}}],"responses":{"200":{"description":"Rotated.","content":{"application/json":{"schema":{"type":"object","properties":{"webhook":{"allOf":[{"$ref":"#/components/schemas/Webhook"},{"type":"object","properties":{"secret":{"type":"string"}},"required":["secret"]}]}},"required":["webhook"]}}}},"401":{"description":"Missing, invalid, expired or revoked credentials.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Authenticated, but not allowed: not a member, insufficient project role or the API token lacks the required scope.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"Not found, or not visible to the caller (tenancy is enforced: foreign resources look identical to missing ones).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Rate limited. Back off and retry after the window in the `RateLimit-*` headers.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}},"tags":["Webhooks"],"x-token-scope":"write"}},"/workspaces/{workspaceId}/webhooks/{id}/test":{"post":{"operationId":"testWebhook","summary":"Send a test delivery now","description":"Posts a synthetic `webhook.test` event to the endpoint synchronously and reports the outcome. Does not touch the cursor or failure state.","parameters":[{"name":"workspaceId","in":"path","required":true,"description":"Workspace id.","schema":{"type":"string","format":"uuid"}},{"name":"id","in":"path","required":true,"description":"Webhook id.","schema":{"type":"string","format":"uuid"}}],"responses":{"200":{"description":"Endpoint accepted the delivery.","content":{"application/json":{"schema":{"type":"object","properties":{"ok":{"type":"boolean"},"response_status":{"type":["integer","null"]},"error":{"type":["string","null"]},"duration_ms":{"type":"integer"}},"required":["ok","duration_ms"]}}}},"401":{"description":"Missing, invalid, expired or revoked credentials.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Authenticated, but not allowed: not a member, insufficient project role or the API token lacks the required scope.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"Not found, or not visible to the caller (tenancy is enforced: foreign resources look identical to missing ones).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"409":{"description":"Secret can no longer be decrypted; rotate it first.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Rate limited. Back off and retry after the window in the `RateLimit-*` headers.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"502":{"description":"Endpoint unreachable or responded non-2xx (same body shape, `ok: false`).","content":{"application/json":{"schema":{"type":"object","properties":{"ok":{"type":"boolean"},"response_status":{"type":["integer","null"]},"error":{"type":["string","null"]},"duration_ms":{"type":"integer"}},"required":["ok","duration_ms"]}}}}},"tags":["Webhooks"],"x-token-scope":"write"}},"/workspaces/{workspaceId}/webhooks/{id}/deliveries":{"get":{"operationId":"listWebhookDeliveries","summary":"Recent delivery attempts","parameters":[{"name":"workspaceId","in":"path","required":true,"description":"Workspace id.","schema":{"type":"string","format":"uuid"}},{"name":"id","in":"path","required":true,"description":"Webhook id.","schema":{"type":"string","format":"uuid"}},{"name":"limit","in":"query","required":false,"description":"Rows to return, newest first.","schema":{"type":"integer","minimum":1,"maximum":200,"default":50}}],"responses":{"200":{"description":"Delivery log.","content":{"application/json":{"schema":{"type":"object","properties":{"deliveries":{"type":"array","items":{"$ref":"#/components/schemas/WebhookDeliveryLog"}}},"required":["deliveries"]}}}},"401":{"description":"Missing, invalid, expired or revoked credentials.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Authenticated, but not allowed: not a member, insufficient project role or the API token lacks the required scope.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"Not found, or not visible to the caller (tenancy is enforced: foreign resources look identical to missing ones).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Rate limited. Back off and retry after the window in the `RateLimit-*` headers.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}},"tags":["Webhooks"],"x-token-scope":"read"}},"/workspaces/{workspaceId}/service-accounts":{"get":{"operationId":"listServiceAccounts","summary":"List service accounts","description":"Workspace owner only. Deactivated accounts are omitted.","parameters":[{"name":"workspaceId","in":"path","required":true,"description":"Workspace id.","schema":{"type":"string","format":"uuid"}}],"responses":{"200":{"description":"Service accounts.","content":{"application/json":{"schema":{"type":"object","properties":{"service_accounts":{"type":"array","items":{"$ref":"#/components/schemas/ServiceAccount"}}},"required":["service_accounts"]}}}},"401":{"description":"Missing, invalid, expired or revoked credentials.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Authenticated, but not allowed: not a member, insufficient project role or the API token lacks the required scope.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Rate limited. Back off and retry after the window in the `RateLimit-*` headers.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}},"tags":["Service accounts"],"x-token-scope":"read"},"post":{"operationId":"createServiceAccount","summary":"Create a service account","description":"Workspace owner only, max 10 per workspace. A service account is a machine identity: it authenticates only with its `tf_` tokens, cannot log in, is confined to this workspace's projects (granted here as an editor; restrict further with token scopes) and appears under its own name as the actor in activity, events and webhooks. It cannot manage tokens, memories, email preferences or create workspaces. The `tf_` plaintext is returned ONCE and never again; store it.","parameters":[{"name":"workspaceId","in":"path","required":true,"description":"Workspace id.","schema":{"type":"string","format":"uuid"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"name":{"type":"string","minLength":1,"maxLength":80,"description":"Actor display name, e.g. \"CRM sync\"."},"project_ids":{"type":"array","items":{"type":"string","format":"uuid"},"maxItems":100,"description":"Projects (of this workspace) to grant. Can be changed later."},"scopes":{"type":"array","items":{"$ref":"#/components/schemas/TokenScope"},"minItems":1,"description":"Scopes of the first token. Default `[\"read\",\"write\"]`."}},"required":["name"]}}}},"responses":{"201":{"description":"Created, with its first token.","content":{"application/json":{"schema":{"type":"object","properties":{"service_account":{"allOf":[{"$ref":"#/components/schemas/ServiceAccount"},{"type":"object","properties":{"token":{"allOf":[{"$ref":"#/components/schemas/ServiceAccountToken"},{"type":"object","properties":{"plain":{"type":"string","description":"The `tf_` secret. Shown once."}},"required":["plain"]}]}},"required":["token"]}]}},"required":["service_account"]}}}},"400":{"description":"Validation failed: the body or query did not match the schema, or a business rule was violated. `error` says which.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Missing, invalid, expired or revoked credentials.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Authenticated, but not allowed: not a member, insufficient project role or the API token lacks the required scope.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Rate limited. Back off and retry after the window in the `RateLimit-*` headers.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}},"tags":["Service accounts"],"x-token-scope":"write"}},"/workspaces/{workspaceId}/service-accounts/{id}":{"patch":{"operationId":"updateServiceAccount","summary":"Rename or re-scope project access","description":"Workspace owner only. `project_ids` REPLACES the whole grant set; additions and removals are announced on the events stream as `project.member_added` / `project.member_removed` with `service_account: true`.","parameters":[{"name":"workspaceId","in":"path","required":true,"description":"Workspace id.","schema":{"type":"string","format":"uuid"}},{"name":"id","in":"path","required":true,"description":"Service account id.","schema":{"type":"string","format":"uuid"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"name":{"type":"string","minLength":1,"maxLength":80},"project_ids":{"type":"array","items":{"type":"string","format":"uuid"},"maxItems":100}}}}}},"responses":{"200":{"description":"Updated.","content":{"application/json":{"schema":{"type":"object","properties":{"service_account":{"$ref":"#/components/schemas/ServiceAccount"}},"required":["service_account"]}}}},"400":{"description":"Validation failed: the body or query did not match the schema, or a business rule was violated. `error` says which.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Missing, invalid, expired or revoked credentials.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Authenticated, but not allowed: not a member, insufficient project role or the API token lacks the required scope.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"Not found, or not visible to the caller (tenancy is enforced: foreign resources look identical to missing ones).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Rate limited. Back off and retry after the window in the `RateLimit-*` headers.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}},"tags":["Service accounts"],"x-token-scope":"write"},"delete":{"operationId":"deactivateServiceAccount","summary":"Deactivate a service account","description":"Workspace owner only. Deactivates rather than deletes: every token is revoked and project access removed immediately, while past activity keeps its attribution.","parameters":[{"name":"workspaceId","in":"path","required":true,"description":"Workspace id.","schema":{"type":"string","format":"uuid"}},{"name":"id","in":"path","required":true,"description":"Service account id.","schema":{"type":"string","format":"uuid"}}],"responses":{"204":{"description":"Deactivated."},"401":{"description":"Missing, invalid, expired or revoked credentials.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Authenticated, but not allowed: not a member, insufficient project role or the API token lacks the required scope.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"Not found, or not visible to the caller (tenancy is enforced: foreign resources look identical to missing ones).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Rate limited. Back off and retry after the window in the `RateLimit-*` headers.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}},"tags":["Service accounts"],"x-token-scope":"write"}},"/workspaces/{workspaceId}/service-accounts/{id}/tokens":{"post":{"operationId":"mintServiceAccountToken","summary":"Mint an additional token","description":"Workspace owner only, max 5 tokens per service account (rotate by minting then revoking). The `tf_` plaintext is returned ONCE and never again; store it.","parameters":[{"name":"workspaceId","in":"path","required":true,"description":"Workspace id.","schema":{"type":"string","format":"uuid"}},{"name":"id","in":"path","required":true,"description":"Service account id.","schema":{"type":"string","format":"uuid"}}],"requestBody":{"required":false,"content":{"application/json":{"schema":{"type":"object","properties":{"name":{"type":"string","minLength":1,"maxLength":80},"scopes":{"type":"array","items":{"$ref":"#/components/schemas/TokenScope"},"minItems":1,"description":"Default `[\"read\",\"write\"]`."}}}}}},"responses":{"201":{"description":"Minted.","content":{"application/json":{"schema":{"type":"object","properties":{"token":{"allOf":[{"$ref":"#/components/schemas/ServiceAccountToken"},{"type":"object","properties":{"plain":{"type":"string","description":"The `tf_` secret. Shown once."}},"required":["plain"]}]}},"required":["token"]}}}},"400":{"description":"Validation failed: the body or query did not match the schema, or a business rule was violated. `error` says which.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Missing, invalid, expired or revoked credentials.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Authenticated, but not allowed: not a member, insufficient project role or the API token lacks the required scope.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"Not found, or not visible to the caller (tenancy is enforced: foreign resources look identical to missing ones).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Rate limited. Back off and retry after the window in the `RateLimit-*` headers.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}},"tags":["Service accounts"],"x-token-scope":"write"}},"/workspaces/{workspaceId}/service-accounts/{id}/tokens/{tokenId}":{"delete":{"operationId":"revokeServiceAccountToken","summary":"Revoke a token","parameters":[{"name":"workspaceId","in":"path","required":true,"description":"Workspace id.","schema":{"type":"string","format":"uuid"}},{"name":"id","in":"path","required":true,"description":"Service account id.","schema":{"type":"string","format":"uuid"}},{"name":"tokenId","in":"path","required":true,"description":"Token id.","schema":{"type":"string","format":"uuid"}}],"responses":{"204":{"description":"Revoked."},"401":{"description":"Missing, invalid, expired or revoked credentials.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Authenticated, but not allowed: not a member, insufficient project role or the API token lacks the required scope.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"Not found, or not visible to the caller (tenancy is enforced: foreign resources look identical to missing ones).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Rate limited. Back off and retry after the window in the `RateLimit-*` headers.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}},"tags":["Service accounts"],"x-token-scope":"write"}},"/users":{"get":{"operationId":"listUsers","summary":"List assignable people","description":"Must be scoped: `project_id` returns that project's active members (max 50); otherwise `workspace_id` returns people in the workspace (max 20). Unscoped calls return an empty list by design: there is no global directory.","parameters":[{"name":"project_id","in":"query","required":false,"description":"Members of this project (takes precedence).","schema":{"type":"string","format":"uuid"}},{"name":"workspace_id","in":"query","required":false,"description":"People in this workspace.","schema":{"type":"string","format":"uuid"}},{"name":"search","in":"query","required":false,"description":"Case-insensitive match on name or email.","schema":{"type":"string"}}],"responses":{"200":{"description":"People.","content":{"application/json":{"schema":{"type":"object","properties":{"users":{"type":"array","items":{"$ref":"#/components/schemas/UserSummary"}}},"required":["users"]}}}},"401":{"description":"Missing, invalid, expired or revoked credentials.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Authenticated, but not allowed: not a member, insufficient project role or the API token lacks the required scope.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Rate limited. Back off and retry after the window in the `RateLimit-*` headers.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}},"tags":["Users"],"x-token-scope":"read"}},"/users/me":{"get":{"operationId":"getMe","summary":"Get my profile","responses":{"200":{"description":"Me.","content":{"application/json":{"schema":{"type":"object","properties":{"user":{"$ref":"#/components/schemas/User"}},"required":["user"]}}}},"401":{"description":"Missing, invalid, expired or revoked credentials.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Authenticated, but not allowed: not a member, insufficient project role or the API token lacks the required scope.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Rate limited. Back off and retry after the window in the `RateLimit-*` headers.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}},"tags":["Users"],"x-token-scope":"read"},"patch":{"operationId":"updateMe","summary":"Update my profile","description":"Email, role and status cannot be changed here.","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"full_name":{"type":"string","minLength":2,"maxLength":100},"avatar_url":{"anyOf":[{"type":"string","format":"uri"},{"type":"null"}]},"job_role":{"anyOf":[{"type":"string","maxLength":80,"description":"Free-text job role, e.g. `Commercial Director`. Trimmed; empty or null clears it."},{"type":"null"}]},"timezone":{"type":"string"}}}}}},"responses":{"200":{"description":"Updated.","content":{"application/json":{"schema":{"type":"object","properties":{"user":{"$ref":"#/components/schemas/User"}},"required":["user"]}}}},"400":{"description":"Validation failed: the body or query did not match the schema, or a business rule was violated. `error` says which.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Missing, invalid, expired or revoked credentials.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Authenticated, but not allowed: not a member, insufficient project role or the API token lacks the required scope.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Rate limited. Back off and retry after the window in the `RateLimit-*` headers.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}},"tags":["Users"],"x-token-scope":"write"}},"/users/me/onboarding/complete":{"post":{"operationId":"completeOnboarding","summary":"Mark onboarding complete","responses":{"204":{"description":"Recorded."},"401":{"description":"Missing, invalid, expired or revoked credentials.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Authenticated, but not allowed: not a member, insufficient project role or the API token lacks the required scope.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Rate limited. Back off and retry after the window in the `RateLimit-*` headers.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}},"tags":["Users"],"x-token-scope":"write"}},"/search":{"get":{"operationId":"search","summary":"Quick search (tasks + projects)","description":"Title/name `contains` search within one workspace, limited to the caller's projects. Both parameters are effectively required: without them the result is empty.","parameters":[{"name":"q","in":"query","required":true,"description":"Search text.","schema":{"type":"string"}},{"name":"workspace_id","in":"query","required":true,"description":"Workspace to search in.","schema":{"type":"string","format":"uuid"}}],"responses":{"200":{"description":"Matches.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SearchResults"}}}},"401":{"description":"Missing, invalid, expired or revoked credentials.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Authenticated, but not allowed: not a member, insufficient project role or the API token lacks the required scope.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Rate limited. Back off and retry after the window in the `RateLimit-*` headers.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}},"tags":["Users"],"x-token-scope":"read"}},"/notifications":{"get":{"operationId":"listNotifications","summary":"List my notifications","parameters":[{"name":"unread_only","in":"query","required":false,"description":"Only unread when exactly `true`.","schema":{"type":"string","enum":["true","false"]}},{"name":"limit","in":"query","required":false,"description":"Page size. Values above 200 are clamped.","schema":{"type":"integer","minimum":1,"maximum":200,"default":50}},{"name":"offset","in":"query","required":false,"description":"Rows to skip.","schema":{"type":"integer","minimum":0,"default":0}}],"responses":{"200":{"description":"Newest first. `unread_count` is the caller's total unread regardless of filters.","content":{"application/json":{"schema":{"type":"object","properties":{"notifications":{"type":"array","items":{"$ref":"#/components/schemas/Notification"}},"unread_count":{"type":"integer"}},"required":["notifications","unread_count"]}}}},"401":{"description":"Missing, invalid, expired or revoked credentials.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Authenticated, but not allowed: not a member, insufficient project role or the API token lacks the required scope.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Rate limited. Back off and retry after the window in the `RateLimit-*` headers.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}},"tags":["Notifications"],"x-token-scope":"read"}},"/notifications/{id}/read":{"post":{"operationId":"markNotificationRead","summary":"Mark one notification read","description":"Idempotent; unknown ids are a silent 200.","parameters":[{"name":"id","in":"path","required":true,"description":"Notification id.","schema":{"type":"string","format":"uuid"}}],"responses":{"200":{"description":"Done.","content":{"application/json":{"schema":{"type":"object","properties":{"ok":{"type":"boolean"}},"required":["ok"]}}}},"401":{"description":"Missing, invalid, expired or revoked credentials.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Authenticated, but not allowed: not a member, insufficient project role or the API token lacks the required scope.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Rate limited. Back off and retry after the window in the `RateLimit-*` headers.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}},"tags":["Notifications"],"x-token-scope":"write"}},"/notifications/read-all":{"post":{"operationId":"markAllNotificationsRead","summary":"Mark all notifications read","responses":{"200":{"description":"Done.","content":{"application/json":{"schema":{"type":"object","properties":{"ok":{"type":"boolean"}},"required":["ok"]}}}},"401":{"description":"Missing, invalid, expired or revoked credentials.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Authenticated, but not allowed: not a member, insufficient project role or the API token lacks the required scope.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Rate limited. Back off and retry after the window in the `RateLimit-*` headers.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}},"tags":["Notifications"],"x-token-scope":"write"}},"/users/me/email-preferences":{"get":{"operationId":"getEmailPreferences","summary":"Get my email preferences","description":"Creates the default row on first read.","responses":{"200":{"description":"Preferences.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/EmailPreferences"}}}},"401":{"description":"Missing, invalid, expired or revoked credentials.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Authenticated, but not allowed: not a member, insufficient project role or the API token lacks the required scope.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Rate limited. Back off and retry after the window in the `RateLimit-*` headers.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}},"tags":["Email preferences"],"x-token-scope":"read"},"patch":{"operationId":"updateEmailPreferences","summary":"Update my email preferences","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"email_enabled":{"type":"boolean"},"digest_frequency":{"type":"string","enum":["daily_weekdays","weekly","never"]},"digest_day":{"type":"integer","minimum":0,"maximum":6},"digest_hour":{"type":"integer","minimum":0,"maximum":23}}}}}},"responses":{"200":{"description":"Updated.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/EmailPreferences"}}}},"400":{"description":"Validation failed: the body or query did not match the schema, or a business rule was violated. `error` says which.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Missing, invalid, expired or revoked credentials.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Authenticated, but not allowed: not a member, insufficient project role or the API token lacks the required scope.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Rate limited. Back off and retry after the window in the `RateLimit-*` headers.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}},"tags":["Email preferences"],"x-token-scope":"write"}},"/invites/{token}":{"get":{"operationId":"previewInvite","summary":"Preview an invite","description":"No auth. Rate-limited (30/15 min per IP). Tells the client whether the invitee already has an account.","parameters":[{"name":"token","in":"path","required":true,"description":"Invite token from the email link.","schema":{"type":"string"}}],"responses":{"200":{"description":"Invite.","content":{"application/json":{"schema":{"type":"object","properties":{"invite":{"$ref":"#/components/schemas/InvitePreview"}},"required":["invite"]}}}},"404":{"description":"Not found, or not visible to the caller (tenancy is enforced: foreign resources look identical to missing ones).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"410":{"description":"Already used or expired.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Too many lookups.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}},"tags":["Invites"],"security":[]}},"/invites/{token}/accept":{"post":{"operationId":"acceptInvite","summary":"Accept an invite","description":"No auth. Rate-limited (10/15 min per IP). Existing accounts supply their password; new accounts supply `name` + a strong password and are created as `guest`. Joins the project and returns session tokens. Responds 200 even when an account was created.","parameters":[{"name":"token","in":"path","required":true,"description":"Invite token from the email link.","schema":{"type":"string"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"name":{"type":"string","minLength":1,"maxLength":255,"description":"Required for new accounts."},"password":{"type":"string","minLength":1}},"required":["password"]}}}},"responses":{"200":{"description":"Joined.","content":{"application/json":{"schema":{"type":"object","properties":{"user":{"type":"object","properties":{"id":{"type":"string","format":"uuid"},"email":{"type":"string"},"full_name":{"type":"string"},"avatar_url":{"anyOf":[{"type":"string"},{"type":"null"}]},"role":{"$ref":"#/components/schemas/UserRole"},"timezone":{"type":"string"}}},"workspaces":{"type":"array","items":{"type":"object","properties":{"id":{"type":"string","format":"uuid"},"name":{"type":"string"}},"required":["id","name"]}},"token":{"type":"string","description":"Access JWT."},"refreshToken":{"type":"string"},"project_id":{"type":"string","format":"uuid"}},"required":["user","workspaces","token","refreshToken","project_id"]}}}},"400":{"description":"Validation failed: the body or query did not match the schema, or a business rule was violated. `error` says which.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Incorrect password.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"Not found, or not visible to the caller (tenancy is enforced: foreign resources look identical to missing ones).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"410":{"description":"Already used or expired.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Too many attempts.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}},"tags":["Invites"],"security":[]}},"/memories":{"get":{"operationId":"listMemories","summary":"List my memories in a workspace","parameters":[{"name":"workspace_id","in":"query","required":true,"description":"Workspace.","schema":{"type":"string","format":"uuid"}}],"responses":{"200":{"description":"Memories.","content":{"application/json":{"schema":{"type":"object","properties":{"memories":{"type":"array","items":{"$ref":"#/components/schemas/Memory"}}},"required":["memories"]}}}},"400":{"description":"Validation failed: the body or query did not match the schema, or a business rule was violated. `error` says which.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Missing, invalid, expired or revoked credentials.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Authenticated, but not allowed: not a member, insufficient project role or the API token lacks the required scope.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Rate limited. Back off and retry after the window in the `RateLimit-*` headers.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}},"tags":["Assistant memory"],"x-token-scope":"read"},"post":{"operationId":"saveMemory","summary":"Save a memory","description":"Requires memory to be enabled for the instance, the workspace AND the caller (opt-in, default off). Scope `user` (default) saves a private memory; near-duplicates refresh the existing row (`dedup_action`), and `supersedes_memory_id` records a correction (the named memory is retired; deleting the correction later reactivates it). Scopes `workspace` and `project` save SHARED team memories through propose-then-approve: they land with status `proposed` (excluded from recall) until a workspace owner (workspace scope) or project owner/editor (project scope) approves them — except a hand-written save by someone who could approve it, which activates immediately. Write gates: workspace scope needs full workspace membership; project scope needs the project owner or editor role. Near-duplicate shared writes are skipped, never refreshed. Rate-limited to 60 requests / 5 min.","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"workspace_id":{"type":"string","format":"uuid"},"content":{"type":"string","minLength":1,"maxLength":240},"kind":{"$ref":"#/components/schemas/MemoryKind"},"importance":{"type":"integer","minimum":1,"maximum":5,"default":3},"project_id":{"type":"string","format":"uuid","description":"Required for project scope."},"source_note":{"type":"string","maxLength":300},"author_type":{"type":"string","enum":["user","agent"],"default":"user"},"supersedes_memory_id":{"type":"string","minLength":4,"maxLength":64,"description":"Full UUID or short ref (e.g. `m_3f2a91`) of one of my own active user-scope memories that this one corrects. User scope only."},"scope":{"type":"string","enum":["user","workspace","project"],"default":"user"},"subject_user_id":{"type":"string","format":"uuid","description":"Who the fact is about — must be tied to the workspace (a project member, direct workspace member or the owner). Shared scopes only."}},"required":["workspace_id","content","kind"]}}}},"responses":{"200":{"description":"Refreshed an existing near-duplicate, or skipped: a user-authored memory already says this, or a matching team memory already exists or awaits approval.","content":{"application/json":{"schema":{"oneOf":[{"allOf":[{"$ref":"#/components/schemas/Memory"},{"type":"object","properties":{"dedup_action":{"type":"string","enum":["refreshed"]}}}]},{"type":"object","properties":{"ok":{"type":"boolean","description":"Always `false` for a skip."},"action":{"type":"string","enum":["skipped"]},"reason":{"type":"string"}},"required":["ok","action","reason"]}]}}}},"201":{"description":"Inserted (status `proposed` for shared-scope writes pending approval).","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/Memory"},{"type":"object","properties":{"dedup_action":{"type":"string","enum":["inserted"]},"superseded_memory_id":{"type":"string","format":"uuid","description":"Present when the write superseded an older memory."}}}]}}}},"400":{"description":"Validation failed: the body or query did not match the schema, or a business rule was violated. `error` says which.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Missing, invalid, expired or revoked credentials.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Authenticated, but not allowed: not a member, insufficient project role or the API token lacks the required scope.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"Not found, or not visible to the caller (tenancy is enforced: foreign resources look identical to missing ones).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Rate limited. Back off and retry after the window in the `RateLimit-*` headers.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}},"tags":["Assistant memory"],"x-token-scope":"write"}},"/memories/search":{"get":{"operationId":"searchMemories","summary":"Search my visible memories","description":"Lexical (case-insensitive substring) search over the memories visible to the caller in a workspace — the same slice recall can inject. Requires memory to be enabled for the instance, the workspace and the caller.","parameters":[{"name":"workspace_id","in":"query","required":true,"description":"Workspace.","schema":{"type":"string","format":"uuid"}},{"name":"q","in":"query","required":true,"description":"Words to look for in memory content.","schema":{"type":"string","minLength":2,"maxLength":120}},{"name":"project_id","in":"query","required":false,"description":"Include this project's project-scope memories.","schema":{"type":"string","format":"uuid"}}],"responses":{"200":{"description":"Matches, most recently used first (max 10).","content":{"application/json":{"schema":{"type":"object","properties":{"memories":{"type":"array","items":{"$ref":"#/components/schemas/Memory"}}},"required":["memories"]}}}},"400":{"description":"Validation failed: the body or query did not match the schema, or a business rule was violated. `error` says which.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Missing, invalid, expired or revoked credentials.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Authenticated, but not allowed: not a member, insufficient project role or the API token lacks the required scope.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Rate limited. Back off and retry after the window in the `RateLimit-*` headers.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}},"tags":["Assistant memory"],"x-token-scope":"read"}},"/memories/delete-for-member":{"post":{"operationId":"deleteMemoriesForMember","summary":"Delete every memory about a member (owner only)","description":"Workspace owner only: hard-deletes every memory in the workspace whose subject is the named user, any scope or status. The offboarding/DSAR action (memories are also cleaned automatically when a member loses access to the workspace).","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"workspace_id":{"type":"string","format":"uuid"},"user_id":{"type":"string","format":"uuid"}},"required":["workspace_id","user_id"]}}}},"responses":{"200":{"description":"Deleted.","content":{"application/json":{"schema":{"type":"object","properties":{"ok":{"type":"boolean"},"deleted":{"type":"integer"}},"required":["ok","deleted"]}}}},"400":{"description":"Validation failed: the body or query did not match the schema, or a business rule was violated. `error` says which.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Missing, invalid, expired or revoked credentials.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Authenticated, but not allowed: not a member, insufficient project role or the API token lacks the required scope.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"Not found, or not visible to the caller (tenancy is enforced: foreign resources look identical to missing ones).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Rate limited. Back off and retry after the window in the `RateLimit-*` headers.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}},"tags":["Assistant memory"],"x-token-scope":"write"}},"/memories/proposals":{"get":{"operationId":"listMemoryProposals","summary":"Proposed team memories I can approve","description":"Workspace-scope proposals for the workspace owner; project-scope proposals for that project's owners and editors. Empty for everyone else.","parameters":[{"name":"workspace_id","in":"query","required":true,"description":"Workspace.","schema":{"type":"string","format":"uuid"}}],"responses":{"200":{"description":"Proposals with display names.","content":{"application/json":{"schema":{"type":"object","properties":{"proposals":{"type":"array","items":{"allOf":[{"$ref":"#/components/schemas/Memory"},{"type":"object","properties":{"author_name":{"type":["string","null"]},"subject_name":{"type":["string","null"]},"project_name":{"type":["string","null"]}}}]}}},"required":["proposals"]}}}},"400":{"description":"Validation failed: the body or query did not match the schema, or a business rule was violated. `error` says which.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Missing, invalid, expired or revoked credentials.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Authenticated, but not allowed: not a member, insufficient project role or the API token lacks the required scope.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Rate limited. Back off and retry after the window in the `RateLimit-*` headers.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}},"tags":["Assistant memory"],"x-token-scope":"read"}},"/memories/{id}/approve":{"post":{"operationId":"approveMemoryProposal","summary":"Approve a proposed team memory","description":"Approver only (workspace owner, or project owner/editor for project scope). The memory becomes active and enters recall; if it names a member, the subject is notified.","parameters":[{"name":"id","in":"path","required":true,"description":"Memory id.","schema":{"type":"string","format":"uuid"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"workspace_id":{"type":"string","format":"uuid"}},"required":["workspace_id"]}}}},"responses":{"200":{"description":"Now active.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Memory"}}}},"400":{"description":"Validation failed: the body or query did not match the schema, or a business rule was violated. `error` says which.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Missing, invalid, expired or revoked credentials.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Authenticated, but not allowed: not a member, insufficient project role or the API token lacks the required scope.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"Not found, or not visible to the caller (tenancy is enforced: foreign resources look identical to missing ones).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Rate limited. Back off and retry after the window in the `RateLimit-*` headers.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}},"tags":["Assistant memory"],"x-token-scope":"write"}},"/memories/{id}/reject":{"post":{"operationId":"rejectMemoryProposal","summary":"Reject a proposed team memory","description":"Approver only. The proposal is permanently deleted (audited without content).","parameters":[{"name":"id","in":"path","required":true,"description":"Memory id.","schema":{"type":"string","format":"uuid"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"workspace_id":{"type":"string","format":"uuid"}},"required":["workspace_id"]}}}},"responses":{"200":{"description":"Deleted.","content":{"application/json":{"schema":{"type":"object","properties":{"ok":{"type":"boolean"},"rejected_memory_id":{"type":"string","format":"uuid"}},"required":["ok","rejected_memory_id"]}}}},"400":{"description":"Validation failed: the body or query did not match the schema, or a business rule was violated. `error` says which.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Missing, invalid, expired or revoked credentials.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Authenticated, but not allowed: not a member, insufficient project role or the API token lacks the required scope.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"Not found, or not visible to the caller (tenancy is enforced: foreign resources look identical to missing ones).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Rate limited. Back off and retry after the window in the `RateLimit-*` headers.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}},"tags":["Assistant memory"],"x-token-scope":"write"}},"/memories/forget":{"post":{"operationId":"forgetMemory","summary":"Forget a memory by id or short prefix","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"ref":{"type":"string","minLength":4,"maxLength":64,"description":"Full UUID or the short ref (e.g. `m_3f2a91`) shown to the assistant."},"workspace_id":{"type":"string","format":"uuid"}},"required":["ref","workspace_id"]}}}},"responses":{"200":{"description":"Deleted.","content":{"application/json":{"schema":{"type":"object","properties":{"ok":{"type":"boolean"},"deleted_memory_id":{"type":"string","format":"uuid"}},"required":["ok","deleted_memory_id"]}}}},"400":{"description":"Validation failed: the body or query did not match the schema, or a business rule was violated. `error` says which.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Missing, invalid, expired or revoked credentials.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Authenticated, but not allowed: not a member, insufficient project role or the API token lacks the required scope.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"Not found, or not visible to the caller (tenancy is enforced: foreign resources look identical to missing ones).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Rate limited. Back off and retry after the window in the `RateLimit-*` headers.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}},"tags":["Assistant memory"],"x-token-scope":"write"}},"/memories/about-me":{"get":{"operationId":"listMemoriesAboutMe","summary":"Everything remembered about me, across workspaces","description":"Subject-access view (GDPR Art. 15): works even if memory is switched off.","responses":{"200":{"description":"Memories with their workspace name.","content":{"application/json":{"schema":{"type":"object","properties":{"memories":{"type":"array","items":{"allOf":[{"$ref":"#/components/schemas/Memory"},{"type":"object","properties":{"workspace_name":{"type":["string","null"]}}}]}}},"required":["memories"]}}}},"401":{"description":"Missing, invalid, expired or revoked credentials.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Authenticated, but not allowed: not a member, insufficient project role or the API token lacks the required scope.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Rate limited. Back off and retry after the window in the `RateLimit-*` headers.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}},"tags":["Assistant memory"],"x-token-scope":"read"}},"/memories/export":{"get":{"operationId":"exportMemories","summary":"Export my memories for a workspace (JSON download)","parameters":[{"name":"workspace_id","in":"query","required":true,"description":"Workspace.","schema":{"type":"string","format":"uuid"}}],"responses":{"200":{"description":"Sent as an attachment.","content":{"application/json":{"schema":{"type":"object","properties":{"exported_at":{"type":"string","format":"date-time"},"workspace_id":{"type":"string","format":"uuid"},"memories":{"type":"array","items":{"$ref":"#/components/schemas/Memory"}}},"required":["exported_at","workspace_id","memories"]}}}},"400":{"description":"Validation failed: the body or query did not match the schema, or a business rule was violated. `error` says which.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Missing, invalid, expired or revoked credentials.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Authenticated, but not allowed: not a member, insufficient project role or the API token lacks the required scope.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Rate limited. Back off and retry after the window in the `RateLimit-*` headers.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}},"tags":["Assistant memory"],"x-token-scope":"read"}},"/memories/preference":{"get":{"operationId":"getMemoryPreference","summary":"Is memory on for my account?","responses":{"200":{"description":"Default `false`.","content":{"application/json":{"schema":{"type":"object","properties":{"enabled":{"type":"boolean"}},"required":["enabled"]}}}},"401":{"description":"Missing, invalid, expired or revoked credentials.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Authenticated, but not allowed: not a member, insufficient project role or the API token lacks the required scope.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Rate limited. Back off and retry after the window in the `RateLimit-*` headers.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}},"tags":["Assistant memory"],"x-token-scope":"read"},"put":{"operationId":"setMemoryPreference","summary":"Opt in or out of memory","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"enabled":{"type":"boolean"}},"required":["enabled"]}}}},"responses":{"200":{"description":"Saved.","content":{"application/json":{"schema":{"type":"object","properties":{"enabled":{"type":"boolean"}},"required":["enabled"]}}}},"400":{"description":"Validation failed: the body or query did not match the schema, or a business rule was violated. `error` says which.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Missing, invalid, expired or revoked credentials.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Authenticated, but not allowed: not a member, insufficient project role or the API token lacks the required scope.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Rate limited. Back off and retry after the window in the `RateLimit-*` headers.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}},"tags":["Assistant memory"],"x-token-scope":"write"}},"/memories/workspace-toggle":{"put":{"operationId":"setWorkspaceMemoryEnabled","summary":"Enable or disable memory for a workspace","description":"Workspace owner only.","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"workspace_id":{"type":"string","format":"uuid"},"enabled":{"type":"boolean"}},"required":["workspace_id","enabled"]}}}},"responses":{"200":{"description":"Saved.","content":{"application/json":{"schema":{"type":"object","properties":{"workspace_id":{"type":"string","format":"uuid"},"memory_enabled":{"type":"boolean"}},"required":["workspace_id","memory_enabled"]}}}},"400":{"description":"Validation failed: the body or query did not match the schema, or a business rule was violated. `error` says which.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Missing, invalid, expired or revoked credentials.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Authenticated, but not allowed: not a member, insufficient project role or the API token lacks the required scope.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"Not found, or not visible to the caller (tenancy is enforced: foreign resources look identical to missing ones).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Rate limited. Back off and retry after the window in the `RateLimit-*` headers.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}},"tags":["Assistant memory"],"x-token-scope":"write"}},"/memories/delete-all":{"post":{"operationId":"deleteAllMemories","summary":"Delete all my memories in a workspace","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"workspace_id":{"type":"string","format":"uuid"},"scope":{"type":"string","enum":["user"],"default":"user"}},"required":["workspace_id"]}}}},"responses":{"200":{"description":"Deleted.","content":{"application/json":{"schema":{"type":"object","properties":{"ok":{"type":"boolean"},"deleted":{"type":"integer"}},"required":["ok","deleted"]}}}},"400":{"description":"Validation failed: the body or query did not match the schema, or a business rule was violated. `error` says which.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Missing, invalid, expired or revoked credentials.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Authenticated, but not allowed: not a member, insufficient project role or the API token lacks the required scope.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Rate limited. Back off and retry after the window in the `RateLimit-*` headers.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}},"tags":["Assistant memory"],"x-token-scope":"write"}},"/memories/{id}":{"patch":{"operationId":"updateMemory","summary":"Edit a memory's text","description":"Marks it user-authored so the assistant can no longer overwrite it.","parameters":[{"name":"id","in":"path","required":true,"description":"Memory id.","schema":{"type":"string","format":"uuid"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"content":{"type":"string","minLength":1,"maxLength":240}},"required":["content"]}}}},"responses":{"200":{"description":"Updated.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Memory"}}}},"400":{"description":"Validation failed: the body or query did not match the schema, or a business rule was violated. `error` says which.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Missing, invalid, expired or revoked credentials.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Authenticated, but not allowed: not a member, insufficient project role or the API token lacks the required scope.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"Not found, or not visible to the caller (tenancy is enforced: foreign resources look identical to missing ones).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Rate limited. Back off and retry after the window in the `RateLimit-*` headers.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}},"tags":["Assistant memory"],"x-token-scope":"write"},"delete":{"operationId":"deleteMemory","summary":"Delete a memory","description":"Allowed for the memory's subject (any scope or status), the author of a still-proposed row, the workspace owner on workspace-scope rows and project owners/editors on project-scope rows. Deleting a superseding memory reactivates the one it replaced.","parameters":[{"name":"id","in":"path","required":true,"description":"Memory id.","schema":{"type":"string","format":"uuid"}}],"responses":{"200":{"description":"Deleted (200 with a body, not 204).","content":{"application/json":{"schema":{"type":"object","properties":{"ok":{"type":"boolean"},"deleted_memory_id":{"type":"string","format":"uuid"}},"required":["ok","deleted_memory_id"]}}}},"401":{"description":"Missing, invalid, expired or revoked credentials.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Authenticated, but not allowed: not a member, insufficient project role or the API token lacks the required scope.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"Not found, or not visible to the caller (tenancy is enforced: foreign resources look identical to missing ones).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Rate limited. Back off and retry after the window in the `RateLimit-*` headers.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}},"tags":["Assistant memory"],"x-token-scope":"write"}}}}