{
  "info": {
    "name": "Klaarin REST API",
    "description": "Klaarin's public REST API (60 endpoints) — projects, tasks, comments, time tracking, workspaces, dashboard, notifications, feature flags, lookups and attachments — plus the 3 endpoints in **Authentication** that register an account and issue/refresh a session token (63 requests total). Generated from the same contract published at https://developer.klaarin.com/rest — see that site for full parameter docs.\n\n**Not live yet.** This documents the finalized /v1 contract ahead of launch. Every workspace plan currently has API access turned off, so the 60 endpoints above will fail with a 403 and `error.code: \"forbidden_plan\"` until Klaarin enables it for Medium/Pro plans. Once it's live, set the `TOKEN_OR_API_KEY` collection variable to a key from Klaarin → Connected Apps — every request in this collection inherits it automatically, except Authentication (see below) and the two Workspaces requests that use a session token instead (also see below).\n\n**Authentication is the exception, and it works today.** `POST /auth/register`, `POST /auth/login` and `POST /auth/refresh` aren't behind the plan gate above — they answer any valid credential right now (Register and Log in need none at all). Run Log in, copy the returned `token` into the `SESSION_TOKEN` collection variable, and Refresh token will send it automatically.\n\n**Two Workspaces requests also use `SESSION_TOKEN`, but stay plan-gated.** Unlike Authentication, `GET /workspaces` and `POST /workspaces/{id}/switch` sit behind the same plan gate as everything else above — they'll 403 until launch too. They send `SESSION_TOKEN` instead of `TOKEN_OR_API_KEY` for a different reason: an API key or OAuth connection is permanently pinned to the workspace it was created in, so \"list my workspaces\" and \"switch workspace\" only make sense for a session token from Log in. The other two Workspaces requests — `GET /workspaces/{id}/usage` and `GET /workspaces/{id}/subscription` — take a normal API key like every other endpoint here; they're restricted instead to the credential's own already-active workspace.",
    "schema": "https://schema.getpostman.com/json/collection/v2.1.0/collection.json"
  },
  "auth": {
    "type": "bearer",
    "bearer": [
      {
        "key": "token",
        "value": "{{TOKEN_OR_API_KEY}}",
        "type": "string"
      }
    ]
  },
  "variable": [
    {
      "key": "BASE_URL",
      "value": "https://api.klaarin.com/v1",
      "type": "string"
    },
    {
      "key": "TOKEN_OR_API_KEY",
      "value": "kl_live_replace_with_your_key",
      "type": "string"
    },
    {
      "key": "SESSION_TOKEN",
      "value": "paste_token_from_login_response_here",
      "type": "string"
    }
  ],
  "item": [
    {
      "name": "Authentication",
      "description": "Register, log in, or refresh a session token. Unlike every other folder in this collection, these requests do NOT use the TOKEN_OR_API_KEY variable — Register and Log in need no credential at all, and Refresh token sends the SESSION_TOKEN variable instead. Not gated by workspace plan, unlike everything else here — see the collection description.",
      "item": [
        {
          "name": "Register",
          "request": {
            "method": "POST",
            "header": [
              {
                "key": "Content-Type",
                "value": "application/json"
              }
            ],
            "url": {
              "raw": "{{BASE_URL}}/auth/register",
              "host": [
                "{{BASE_URL}}"
              ],
              "path": [
                "auth",
                "register"
              ]
            },
            "description": "Creates a new Klaarin account. Like login, this takes no credential — added 2026-09-02, mounted the same way as login/refresh. Unlike login, it does NOT return a token: the account needs email verification first (a welcome email is sent with a verify link), so POST /auth/login is the next step once that's done. A duplicate email returns 400 invalid_request, not a 409 — the closed error-code list (see the REST Overview page) has no dedicated conflict code.",
            "body": {
              "mode": "raw",
              "raw": "{\n  \"email\": \"bayu@floothink.com\",\n  \"password\": \"your-password\",\n  \"name\": \"Review homepage copy\"\n}",
              "options": {
                "raw": {
                  "language": "json"
                }
              }
            },
            "auth": {
              "type": "noauth"
            }
          }
        },
        {
          "name": "Log in",
          "request": {
            "method": "POST",
            "header": [
              {
                "key": "Content-Type",
                "value": "application/json"
              }
            ],
            "url": {
              "raw": "{{BASE_URL}}/auth/login",
              "host": [
                "{{BASE_URL}}"
              ],
              "path": [
                "auth",
                "login"
              ]
            },
            "description": "Exchanges an email and password for a session token scoped to that one user. The only /v1 endpoint that takes no credential — it's how you get one. Rate-limited to 10 attempts per 15 minutes per IP; an unknown email and a wrong password both return the same 401 unauthorized, so it can't be used to discover who has an account.",
            "body": {
              "mode": "raw",
              "raw": "{\n  \"email\": \"bayu@floothink.com\",\n  \"password\": \"your-password\"\n}",
              "options": {
                "raw": {
                  "language": "json"
                }
              }
            },
            "auth": {
              "type": "noauth"
            }
          }
        },
        {
          "name": "Refresh token",
          "request": {
            "method": "POST",
            "header": [],
            "url": {
              "raw": "{{BASE_URL}}/auth/refresh",
              "host": [
                "{{BASE_URL}}"
              ],
              "path": [
                "auth",
                "refresh"
              ]
            },
            "description": "Trades a still-valid session token for a fresh one with a new 7-day expiry. Send the current token as the Bearer, not an API key. Rate-limited to 30 attempts per 15 minutes per IP. There is no grace period — once a token has expired it can't be refreshed, and the user has to log in again.",
            "auth": {
              "type": "bearer",
              "bearer": [
                {
                  "key": "token",
                  "value": "{{SESSION_TOKEN}}",
                  "type": "string"
                }
              ]
            }
          }
        }
      ]
    },
    {
      "name": "Tasks",
      "description": "Create, read, update, archive and restore tasks. Task titles are user content — returned verbatim, never normalised.",
      "item": [
        {
          "name": "List tasks",
          "request": {
            "method": "GET",
            "header": [],
            "url": {
              "raw": "{{BASE_URL}}/tasks?limit=25&offset=0",
              "host": [
                "{{BASE_URL}}"
              ],
              "path": [
                "tasks"
              ],
              "query": [
                {
                  "key": "projectId",
                  "value": "42",
                  "description": "Filter to one project",
                  "disabled": true
                },
                {
                  "key": "status",
                  "value": "todo",
                  "description": "todo · in_progress · internal_review · client_review · completed",
                  "disabled": true
                },
                {
                  "key": "priority",
                  "value": "medium",
                  "description": "low · medium · high",
                  "disabled": true
                },
                {
                  "key": "assigneeId",
                  "value": "",
                  "description": "Filter to one assignee",
                  "disabled": true
                },
                {
                  "key": "search",
                  "value": "homepage",
                  "description": "Matches task name or description",
                  "disabled": true
                },
                {
                  "key": "isArchived",
                  "value": "",
                  "description": "Archived tasks are excluded by default; pass true to include them instead",
                  "disabled": true
                },
                {
                  "key": "limit",
                  "value": "25",
                  "description": "Page size, default 25, max 100",
                  "disabled": false
                },
                {
                  "key": "offset",
                  "value": "0",
                  "description": "Number of results to skip, default 0",
                  "disabled": false
                },
                {
                  "key": "fields",
                  "value": "id,name,status",
                  "description": "Comma-separated field names to trim the response to",
                  "disabled": true
                },
                {
                  "key": "expand",
                  "value": "comments",
                  "description": "Comma-separated relations to include in full — comments",
                  "disabled": true
                },
                {
                  "key": "scope",
                  "value": "mine",
                  "description": "mine — limit to tasks in projects you own or are a member of, plus any task you follow (even outside those projects). Omit for the default (everything visible in the workspace). If projectId is also given, it takes precedence over follow-based inclusion.",
                  "disabled": true
                },
                {
                  "key": "mine",
                  "value": "",
                  "description": "Bare-parameter alias for scope=mine — ignored whenever scope is also present. A session-JWT caller (e.g. the mobile app) already defaults to mine without either param; an API key/OAuth caller defaults to workspace-wide and needs one of these to narrow.",
                  "disabled": true
                }
              ]
            },
            "description": "Lists tasks visible to the key’s user, newest first."
          }
        },
        {
          "name": "Get task",
          "request": {
            "method": "GET",
            "header": [],
            "url": {
              "raw": "{{BASE_URL}}/tasks/:id",
              "host": [
                "{{BASE_URL}}"
              ],
              "path": [
                "tasks",
                ":id"
              ],
              "query": [
                {
                  "key": "fields",
                  "value": "id,name,status",
                  "description": "Comma-separated field names to trim the response to",
                  "disabled": true
                }
              ],
              "variable": [
                {
                  "key": "id",
                  "value": "318",
                  "description": "Example task ID"
                }
              ]
            },
            "description": "Gets full details of one task, including its last 10 comments."
          }
        },
        {
          "name": "Create task",
          "request": {
            "method": "POST",
            "header": [
              {
                "key": "Content-Type",
                "value": "application/json"
              }
            ],
            "url": {
              "raw": "{{BASE_URL}}/tasks",
              "host": [
                "{{BASE_URL}}"
              ],
              "path": [
                "tasks"
              ]
            },
            "description": "Creates a task in a project.",
            "body": {
              "mode": "raw",
              "raw": "{\n  \"projectId\": 42,\n  \"name\": \"Review homepage copy\",\n  \"description\": \"\",\n  \"status\": \"todo\",\n  \"priority\": \"medium\",\n  \"assigneeId\": null,\n  \"categoryId\": null,\n  \"typeId\": null,\n  \"startDate\": null,\n  \"deadline\": null,\n  \"customEstimatedHours\": null\n}",
              "options": {
                "raw": {
                  "language": "json"
                }
              }
            }
          }
        },
        {
          "name": "Update task",
          "request": {
            "method": "PATCH",
            "header": [
              {
                "key": "Content-Type",
                "value": "application/json"
              }
            ],
            "url": {
              "raw": "{{BASE_URL}}/tasks/:id",
              "host": [
                "{{BASE_URL}}"
              ],
              "path": [
                "tasks",
                ":id"
              ],
              "variable": [
                {
                  "key": "id",
                  "value": "318",
                  "description": "Example task ID"
                }
              ]
            },
            "description": "Updates one or more fields. Omitted fields are left unchanged. Tasks aren’t moved to a different project via this endpoint.",
            "body": {
              "mode": "raw",
              "raw": "{\n  \"name\": \"Review homepage copy\",\n  \"description\": \"\",\n  \"status\": \"todo\",\n  \"priority\": \"medium\",\n  \"assigneeId\": null,\n  \"startDate\": null,\n  \"deadline\": null,\n  \"customEstimatedHours\": null\n}",
              "options": {
                "raw": {
                  "language": "json"
                }
              }
            }
          }
        },
        {
          "name": "Archive task",
          "request": {
            "method": "POST",
            "header": [],
            "url": {
              "raw": "{{BASE_URL}}/tasks/:id/archive",
              "host": [
                "{{BASE_URL}}"
              ],
              "path": [
                "tasks",
                ":id",
                "archive"
              ],
              "variable": [
                {
                  "key": "id",
                  "value": "318",
                  "description": "Example task ID"
                }
              ]
            },
            "description": "Archives a task. Archived tasks are read-only until restored."
          }
        },
        {
          "name": "Unarchive task",
          "request": {
            "method": "POST",
            "header": [],
            "url": {
              "raw": "{{BASE_URL}}/tasks/:id/unarchive",
              "host": [
                "{{BASE_URL}}"
              ],
              "path": [
                "tasks",
                ":id",
                "unarchive"
              ],
              "variable": [
                {
                  "key": "id",
                  "value": "318",
                  "description": "Example task ID"
                }
              ]
            },
            "description": "Restores a previously archived task."
          }
        },
        {
          "name": "POST /tasks/{id}/duplicate",
          "request": {
            "method": "POST",
            "header": [],
            "url": {
              "raw": "{{BASE_URL}}/tasks/:id/duplicate",
              "host": [
                "{{BASE_URL}}"
              ],
              "path": [
                "tasks",
                ":id",
                "duplicate"
              ],
              "variable": [
                {
                  "key": "id",
                  "value": "318",
                  "description": "Example task ID"
                }
              ]
            },
            "description": "Creates a copy of a task (\"{name} (copy)\"), rescheduling it to the assignee's next free hour that day when the original had a deadline, and carrying over its followers. Takes no request body."
          }
        },
        {
          "name": "GET /tasks/{id}/activities",
          "request": {
            "method": "GET",
            "header": [],
            "url": {
              "raw": "{{BASE_URL}}/tasks/:id/activities?limit=25&offset=0",
              "host": [
                "{{BASE_URL}}"
              ],
              "path": [
                "tasks",
                ":id",
                "activities"
              ],
              "query": [
                {
                  "key": "limit",
                  "value": "25",
                  "description": "Page size, default 25, max 100",
                  "disabled": false
                },
                {
                  "key": "offset",
                  "value": "0",
                  "description": "Number of results to skip, default 0",
                  "disabled": false
                }
              ],
              "variable": [
                {
                  "key": "id",
                  "value": "318",
                  "description": "Example task ID"
                }
              ]
            },
            "description": "Lists a task's activity log (status changes, assignment changes, comments-added markers, etc.), newest first."
          }
        }
      ]
    },
    {
      "name": "Projects",
      "description": "Read projects owned by or shared with the authorized user, and resolve member names for assignment.",
      "item": [
        {
          "name": "Get current user",
          "request": {
            "method": "GET",
            "header": [],
            "url": {
              "raw": "{{BASE_URL}}/me",
              "host": [
                "{{BASE_URL}}"
              ],
              "path": [
                "me"
              ],
              "query": [
                {
                  "key": "fields",
                  "value": "id,name,status",
                  "description": "Comma-separated field names to trim the response to",
                  "disabled": true
                }
              ]
            },
            "description": "Returns the Klaarin user this key is authorized for, including workspace and plan. Use it to verify a key before anything else."
          }
        },
        {
          "name": "List projects",
          "request": {
            "method": "GET",
            "header": [],
            "url": {
              "raw": "{{BASE_URL}}/projects?limit=25&offset=0",
              "host": [
                "{{BASE_URL}}"
              ],
              "path": [
                "projects"
              ],
              "query": [
                {
                  "key": "limit",
                  "value": "25",
                  "description": "Page size, default 25, max 100",
                  "disabled": false
                },
                {
                  "key": "offset",
                  "value": "0",
                  "description": "Number of results to skip, default 0",
                  "disabled": false
                },
                {
                  "key": "fields",
                  "value": "id,name,status",
                  "description": "Comma-separated field names to trim the response to",
                  "disabled": true
                },
                {
                  "key": "expand",
                  "value": "comments",
                  "description": "Comma-separated relations to include in full — members",
                  "disabled": true
                },
                {
                  "key": "scope",
                  "value": "mine",
                  "description": "mine — limit to projects you created or are a member of. Omit for the default (everything visible in the workspace).",
                  "disabled": true
                }
              ]
            },
            "description": "Lists projects visible to the key’s user."
          }
        },
        {
          "name": "Get project",
          "request": {
            "method": "GET",
            "header": [],
            "url": {
              "raw": "{{BASE_URL}}/projects/:id",
              "host": [
                "{{BASE_URL}}"
              ],
              "path": [
                "projects",
                ":id"
              ],
              "query": [
                {
                  "key": "fields",
                  "value": "id,name,status",
                  "description": "Comma-separated field names to trim the response to",
                  "disabled": true
                },
                {
                  "key": "expand",
                  "value": "comments",
                  "description": "Comma-separated relations to include in full — members",
                  "disabled": true
                }
              ],
              "variable": [
                {
                  "key": "id",
                  "value": "42",
                  "description": "Example project ID"
                }
              ]
            },
            "description": "Gets details of one project, including its member count."
          }
        },
        {
          "name": "List project members",
          "request": {
            "method": "GET",
            "header": [],
            "url": {
              "raw": "{{BASE_URL}}/projects/:id/members",
              "host": [
                "{{BASE_URL}}"
              ],
              "path": [
                "projects",
                ":id",
                "members"
              ],
              "variable": [
                {
                  "key": "id",
                  "value": "42",
                  "description": "Example project ID"
                }
              ]
            },
            "description": "Lists a project’s members and owner — use this to resolve assignee names to IDs. Not paginated."
          }
        }
      ]
    },
    {
      "name": "Comments",
      "description": "Read and add comments on a task’s activity rail.",
      "item": [
        {
          "name": "List comments",
          "request": {
            "method": "GET",
            "header": [],
            "url": {
              "raw": "{{BASE_URL}}/tasks/:id/comments?limit=25&offset=0",
              "host": [
                "{{BASE_URL}}"
              ],
              "path": [
                "tasks",
                ":id",
                "comments"
              ],
              "query": [
                {
                  "key": "limit",
                  "value": "25",
                  "description": "Page size, default 25, max 100",
                  "disabled": false
                },
                {
                  "key": "offset",
                  "value": "0",
                  "description": "Number of results to skip, default 0",
                  "disabled": false
                },
                {
                  "key": "fields",
                  "value": "id,name,status",
                  "description": "Comma-separated field names to trim the response to",
                  "disabled": true
                }
              ],
              "variable": [
                {
                  "key": "id",
                  "value": "318",
                  "description": "Example task ID"
                }
              ]
            },
            "description": "Lists comments on a task, oldest first."
          }
        },
        {
          "name": "Add comment",
          "request": {
            "method": "POST",
            "header": [
              {
                "key": "Content-Type",
                "value": "application/json"
              }
            ],
            "url": {
              "raw": "{{BASE_URL}}/tasks/:id/comments",
              "host": [
                "{{BASE_URL}}"
              ],
              "path": [
                "tasks",
                ":id",
                "comments"
              ],
              "variable": [
                {
                  "key": "id",
                  "value": "318",
                  "description": "Example task ID"
                }
              ]
            },
            "description": "Adds a comment as the key’s user.",
            "body": {
              "mode": "raw",
              "raw": "{\n  \"content\": \"Deployed to staging, please review.\"\n}",
              "options": {
                "raw": {
                  "language": "json"
                }
              }
            }
          }
        }
      ]
    },
    {
      "name": "Time tracking",
      "description": "Read time entries, check the running timer, and control it. Starting a timer stops any timer already running.",
      "item": [
        {
          "name": "List time entries",
          "request": {
            "method": "GET",
            "header": [],
            "url": {
              "raw": "{{BASE_URL}}/time-entries?limit=25&offset=0",
              "host": [
                "{{BASE_URL}}"
              ],
              "path": [
                "time-entries"
              ],
              "query": [
                {
                  "key": "taskId",
                  "value": "318",
                  "description": "Filter to one task",
                  "disabled": true
                },
                {
                  "key": "from",
                  "value": "2026-08-01",
                  "description": "ISO 8601 start of range",
                  "disabled": true
                },
                {
                  "key": "to",
                  "value": "2026-08-31",
                  "description": "ISO 8601 end of range",
                  "disabled": true
                },
                {
                  "key": "limit",
                  "value": "25",
                  "description": "Page size, default 25, max 100",
                  "disabled": false
                },
                {
                  "key": "offset",
                  "value": "0",
                  "description": "Number of results to skip, default 0",
                  "disabled": false
                },
                {
                  "key": "fields",
                  "value": "id,name,status",
                  "description": "Comma-separated field names to trim the response to",
                  "disabled": true
                }
              ]
            },
            "description": "Lists time-tracking entries for the key’s user."
          }
        },
        {
          "name": "Get running timer",
          "request": {
            "method": "GET",
            "header": [],
            "url": {
              "raw": "{{BASE_URL}}/timer",
              "host": [
                "{{BASE_URL}}"
              ],
              "path": [
                "timer"
              ]
            },
            "description": "Returns the key’s user’s currently running entry, or null if none."
          }
        },
        {
          "name": "Start timer",
          "request": {
            "method": "POST",
            "header": [
              {
                "key": "Content-Type",
                "value": "application/json"
              }
            ],
            "url": {
              "raw": "{{BASE_URL}}/timer/start",
              "host": [
                "{{BASE_URL}}"
              ],
              "path": [
                "timer",
                "start"
              ]
            },
            "description": "Starts a timer for a task. Any running timer is stopped first.",
            "body": {
              "mode": "raw",
              "raw": "{\n  \"taskId\": 318\n}",
              "options": {
                "raw": {
                  "language": "json"
                }
              }
            }
          }
        },
        {
          "name": "Stop timer",
          "request": {
            "method": "POST",
            "header": [],
            "url": {
              "raw": "{{BASE_URL}}/timer/stop",
              "host": [
                "{{BASE_URL}}"
              ],
              "path": [
                "timer",
                "stop"
              ]
            },
            "description": "Stops the currently running timer and returns the closed entry."
          }
        },
        {
          "name": "Get time summary",
          "request": {
            "method": "GET",
            "header": [],
            "url": {
              "raw": "{{BASE_URL}}/time-entries/summary",
              "host": [
                "{{BASE_URL}}"
              ],
              "path": [
                "time-entries",
                "summary"
              ],
              "query": [
                {
                  "key": "period",
                  "value": "week",
                  "description": "day · week · month, defaults to week. Any other value falls back to a rolling 7-day window.",
                  "disabled": true
                }
              ]
            },
            "description": "Aggregate totals for the key’s user — total seconds/hours, entry count, and a per-task breakdown."
          }
        }
      ]
    },
    {
      "name": "Workspaces",
      "description": "List the workspaces you belong to and switch which one is active. Session-token only — an API key or OAuth connection is pinned to the workspace it was created in and can't reach these.",
      "item": [
        {
          "name": "List workspaces",
          "request": {
            "method": "GET",
            "header": [],
            "url": {
              "raw": "{{BASE_URL}}/workspaces",
              "host": [
                "{{BASE_URL}}"
              ],
              "path": [
                "workspaces"
              ]
            },
            "description": "Lists the workspaces the caller belongs to, each trimmed to id, name and plan.",
            "auth": {
              "type": "bearer",
              "bearer": [
                {
                  "key": "token",
                  "value": "{{SESSION_TOKEN}}",
                  "type": "string"
                }
              ]
            }
          }
        },
        {
          "name": "Switch workspace",
          "request": {
            "method": "POST",
            "header": [],
            "url": {
              "raw": "{{BASE_URL}}/workspaces/:id/switch",
              "host": [
                "{{BASE_URL}}"
              ],
              "path": [
                "workspaces",
                ":id",
                "switch"
              ],
              "variable": [
                {
                  "key": "id",
                  "value": "clw9z8y7x6w5v4u3t2s1r0",
                  "description": "Example workspace ID"
                }
              ]
            },
            "description": "Switches the caller's active workspace, provided they're already a member. Returns 404 not_found (not 403) if the caller can't access it — the endpoint never confirms a workspace exists to someone who isn't a member.",
            "auth": {
              "type": "bearer",
              "bearer": [
                {
                  "key": "token",
                  "value": "{{SESSION_TOKEN}}",
                  "type": "string"
                }
              ]
            }
          }
        },
        {
          "name": "Get workspace usage",
          "request": {
            "method": "GET",
            "header": [],
            "url": {
              "raw": "{{BASE_URL}}/workspaces/:id/usage",
              "host": [
                "{{BASE_URL}}"
              ],
              "path": [
                "workspaces",
                ":id",
                "usage"
              ],
              "variable": [
                {
                  "key": "id",
                  "value": "clw9z8y7x6w5v4u3t2s1r0",
                  "description": "Example workspace ID"
                }
              ]
            },
            "description": "Users, projects, and storage consumed against the workspace's plan limits. Unlike List/Switch above, this takes any credential — API key, OAuth, or session token — but only for the credential's own active workspace: {id} must match it exactly, or the response is 404 not_found, never 403."
          }
        },
        {
          "name": "Get workspace subscription",
          "request": {
            "method": "GET",
            "header": [],
            "url": {
              "raw": "{{BASE_URL}}/workspaces/:id/subscription",
              "host": [
                "{{BASE_URL}}"
              ],
              "path": [
                "workspaces",
                ":id",
                "subscription"
              ],
              "variable": [
                {
                  "key": "id",
                  "value": "clw9z8y7x6w5v4u3t2s1r0",
                  "description": "Example workspace ID"
                }
              ]
            },
            "description": "The workspace's current plan and subscription status. Same credential and {id}-must-match-active-workspace rules as Usage above. 404s if the workspace has no subscription row."
          }
        }
      ]
    },
    {
      "name": "Dashboard",
      "description": "Aggregate, read-only views that power the Klaarin dashboard — stat cards, the active task list, client comments, and team workload. Every endpoint is scoped to the active workspace and further trimmed by the caller's role, matching the web dashboard's own visibility rules exactly. All four accept the shared filter set below; a parameter is only listed on an endpoint where it changes the result.",
      "item": [
        {
          "name": "Get dashboard stats",
          "request": {
            "method": "GET",
            "header": [],
            "url": {
              "raw": "{{BASE_URL}}/dashboard/stats",
              "host": [
                "{{BASE_URL}}"
              ],
              "path": [
                "dashboard",
                "stats"
              ],
              "query": [
                {
                  "key": "search",
                  "value": "homepage",
                  "description": "Matches task name or description",
                  "disabled": true
                },
                {
                  "key": "from",
                  "value": "2026-08-01",
                  "description": "Earliest task deadline to include (YYYY-MM-DD, inclusive). Intersects with each tile's own window rather than replacing it",
                  "disabled": true
                },
                {
                  "key": "to",
                  "value": "2026-08-31",
                  "description": "Latest task deadline to include (YYYY-MM-DD, inclusive)",
                  "disabled": true
                },
                {
                  "key": "teamId",
                  "value": "3",
                  "description": "Filter to the members of one or more teams, including their sub-teams (comma-separated). Combined with assigneeId it intersects, it does not widen",
                  "disabled": true
                },
                {
                  "key": "assigneeId",
                  "value": "",
                  "description": "Filter to one or more assignees (comma-separated)",
                  "disabled": true
                },
                {
                  "key": "taskId",
                  "value": "318",
                  "description": "Filter to one task",
                  "disabled": true
                },
                {
                  "key": "projectId",
                  "value": "42",
                  "description": "Filter to one project",
                  "disabled": true
                },
                {
                  "key": "projectStatus",
                  "value": "",
                  "description": "Filter to projects whose status matches this value exactly, e.g. in-progress",
                  "disabled": true
                },
                {
                  "key": "status",
                  "value": "todo",
                  "description": "Filter to one task status. Replaces the default not-completed baseline, so a completed status can be counted",
                  "disabled": true
                },
                {
                  "key": "localDate",
                  "value": "2026-09-02",
                  "description": "Caller's local date (YYYY-MM-DD), defaults to server today — pass this to avoid a timezone-boundary mismatch on overdue/due-today/due-this-week",
                  "disabled": true
                }
              ]
            },
            "description": "The four stat-card counts: projects, overdue, due today, due this week."
          }
        },
        {
          "name": "List dashboard tasks",
          "request": {
            "method": "GET",
            "header": [],
            "url": {
              "raw": "{{BASE_URL}}/dashboard/tasks?limit=25&offset=0",
              "host": [
                "{{BASE_URL}}"
              ],
              "path": [
                "dashboard",
                "tasks"
              ],
              "query": [
                {
                  "key": "search",
                  "value": "homepage",
                  "description": "Matches task name or description",
                  "disabled": true
                },
                {
                  "key": "from",
                  "value": "2026-08-01",
                  "description": "Earliest task deadline to include (YYYY-MM-DD, inclusive)",
                  "disabled": true
                },
                {
                  "key": "to",
                  "value": "2026-08-31",
                  "description": "Latest task deadline to include (YYYY-MM-DD, inclusive)",
                  "disabled": true
                },
                {
                  "key": "teamId",
                  "value": "3",
                  "description": "Filter to the members of one or more teams, including their sub-teams (comma-separated). Combined with assigneeId it intersects, it does not widen",
                  "disabled": true
                },
                {
                  "key": "assigneeId",
                  "value": "",
                  "description": "Filter to one or more assignees (comma-separated). Never widens past what the caller's role may see",
                  "disabled": true
                },
                {
                  "key": "taskId",
                  "value": "318",
                  "description": "Filter to one task",
                  "disabled": true
                },
                {
                  "key": "projectId",
                  "value": "42",
                  "description": "Filter to one project",
                  "disabled": true
                },
                {
                  "key": "projectStatus",
                  "value": "",
                  "description": "Filter to projects whose status matches this value exactly, e.g. in-progress",
                  "disabled": true
                },
                {
                  "key": "status",
                  "value": "todo",
                  "description": "Filter to one status value",
                  "disabled": true
                },
                {
                  "key": "sort",
                  "value": "due",
                  "description": "due (default), created, name, or status. status sorts by a fixed pipeline order (todo, in-progress, internal-review, client-review); a status outside that list sorts last",
                  "disabled": true
                },
                {
                  "key": "localDate",
                  "value": "2026-09-02",
                  "description": "Caller's local date (YYYY-MM-DD), for due-state labeling",
                  "disabled": true
                },
                {
                  "key": "limit",
                  "value": "25",
                  "description": "Page size, default 25, max 100",
                  "disabled": false
                },
                {
                  "key": "offset",
                  "value": "0",
                  "description": "Number of results to skip, default 0",
                  "disabled": false
                }
              ]
            },
            "description": "The Active Tasks list — lean task rows for the dashboard, not the full task shape GET /tasks/{id} returns."
          }
        },
        {
          "name": "List dashboard comments",
          "request": {
            "method": "GET",
            "header": [],
            "url": {
              "raw": "{{BASE_URL}}/dashboard/comments?limit=25&offset=0",
              "host": [
                "{{BASE_URL}}"
              ],
              "path": [
                "dashboard",
                "comments"
              ],
              "query": [
                {
                  "key": "search",
                  "value": "homepage",
                  "description": "Matches the comment's own content",
                  "disabled": true
                },
                {
                  "key": "from",
                  "value": "2026-08-01",
                  "description": "Earliest comment date to include (YYYY-MM-DD, inclusive). Unlike the other endpoints this filters when the comment was posted, not a task deadline",
                  "disabled": true
                },
                {
                  "key": "to",
                  "value": "2026-08-31",
                  "description": "Latest comment date to include (YYYY-MM-DD, inclusive)",
                  "disabled": true
                },
                {
                  "key": "teamId",
                  "value": "3",
                  "description": "Filter to comments on tasks assigned to the members of one or more teams, including their sub-teams (comma-separated)",
                  "disabled": true
                },
                {
                  "key": "assigneeId",
                  "value": "",
                  "description": "Filter to comments on tasks assigned to one or more people (comma-separated). This matches the task's assignee, not the comment's author",
                  "disabled": true
                },
                {
                  "key": "projectId",
                  "value": "42",
                  "description": "Filter to one project",
                  "disabled": true
                },
                {
                  "key": "projectStatus",
                  "value": "",
                  "description": "Filter to projects whose status matches this value exactly, e.g. in-progress",
                  "disabled": true
                },
                {
                  "key": "limit",
                  "value": "25",
                  "description": "Page size, default 25, max 100",
                  "disabled": false
                },
                {
                  "key": "offset",
                  "value": "0",
                  "description": "Number of results to skip, default 0",
                  "disabled": false
                }
              ]
            },
            "description": "Client Comments & Mentions. Returns an empty page (not an error) for a role that can't see client comments — currently client."
          }
        },
        {
          "name": "Get dashboard workload",
          "request": {
            "method": "GET",
            "header": [],
            "url": {
              "raw": "{{BASE_URL}}/dashboard/workload",
              "host": [
                "{{BASE_URL}}"
              ],
              "path": [
                "dashboard",
                "workload"
              ],
              "query": [
                {
                  "key": "from",
                  "value": "2026-08-01",
                  "description": "Start of an explicit window (YYYY-MM-DD). Overrides the mode's implicit window for capacity, the completed window, and the daily due-window",
                  "disabled": true
                },
                {
                  "key": "to",
                  "value": "2026-08-31",
                  "description": "End of the explicit window (YYYY-MM-DD). Defaults to from, i.e. a single day",
                  "disabled": true
                },
                {
                  "key": "teamId",
                  "value": "3",
                  "description": "Limit the roster to the members of one or more teams, including their sub-teams (comma-separated)",
                  "disabled": true
                },
                {
                  "key": "assigneeId",
                  "value": "",
                  "description": "Limit the roster to one or more people (comma-separated). Someone picked here appears at 0% rather than being dropped when they have no active task in scope",
                  "disabled": true
                },
                {
                  "key": "projectId",
                  "value": "42",
                  "description": "Filter to one project",
                  "disabled": true
                },
                {
                  "key": "projectStatus",
                  "value": "",
                  "description": "Filter to projects whose status matches this value exactly, e.g. in-progress",
                  "disabled": true
                },
                {
                  "key": "mode",
                  "value": "daily",
                  "description": "daily (default) or monthly",
                  "disabled": true
                }
              ]
            },
            "description": "Per-person capacity/utilization gauge plus a 6-row breakdown, for the whole team visible in the active workspace. Not paginated — one object covering everyone the caller's role allows onto the roster. Returns empty workloads for a role that can't see workload — currently client."
          }
        }
      ]
    },
    {
      "name": "Notifications",
      "description": "Read and manage the key’s user's own notifications. Scoped by user id alone — a notification belongs to one person regardless of which workspace they're active in, so these behave identically for every credential type and have no workspace boundary to enforce.",
      "item": [
        {
          "name": "List notifications",
          "request": {
            "method": "GET",
            "header": [],
            "url": {
              "raw": "{{BASE_URL}}/notifications?limit=25&offset=0",
              "host": [
                "{{BASE_URL}}"
              ],
              "path": [
                "notifications"
              ],
              "query": [
                {
                  "key": "unreadOnly",
                  "value": "true",
                  "description": "true to return only unread notifications",
                  "disabled": true
                },
                {
                  "key": "limit",
                  "value": "25",
                  "description": "Page size, default 25, max 100",
                  "disabled": false
                },
                {
                  "key": "offset",
                  "value": "0",
                  "description": "Number of results to skip, default 0",
                  "disabled": false
                }
              ]
            },
            "description": "Lists the caller's notifications, newest first, with task/comment/project/actor summaries embedded."
          }
        },
        {
          "name": "Get unread count",
          "request": {
            "method": "GET",
            "header": [],
            "url": {
              "raw": "{{BASE_URL}}/notifications/count",
              "host": [
                "{{BASE_URL}}"
              ],
              "path": [
                "notifications",
                "count"
              ]
            },
            "description": "Returns just the caller's unread count, for a badge."
          }
        },
        {
          "name": "Mark notification read",
          "request": {
            "method": "PUT",
            "header": [],
            "url": {
              "raw": "{{BASE_URL}}/notifications/:id/read",
              "host": [
                "{{BASE_URL}}"
              ],
              "path": [
                "notifications",
                ":id",
                "read"
              ],
              "variable": [
                {
                  "key": "id",
                  "value": "9001",
                  "description": "Example notification ID"
                }
              ]
            },
            "description": "Marks one notification read and returns it. 404s — never 403 — for a nonexistent id or one belonging to someone else; the two are indistinguishable to the caller."
          }
        },
        {
          "name": "Mark all notifications read",
          "request": {
            "method": "PUT",
            "header": [],
            "url": {
              "raw": "{{BASE_URL}}/notifications/read-all",
              "host": [
                "{{BASE_URL}}"
              ],
              "path": [
                "notifications",
                "read-all"
              ]
            },
            "description": "Marks every one of the caller's unread notifications read."
          }
        },
        {
          "name": "Delete notification",
          "request": {
            "method": "DELETE",
            "header": [],
            "url": {
              "raw": "{{BASE_URL}}/notifications/:id",
              "host": [
                "{{BASE_URL}}"
              ],
              "path": [
                "notifications",
                ":id"
              ],
              "variable": [
                {
                  "key": "id",
                  "value": "9001",
                  "description": "Example notification ID"
                }
              ]
            },
            "description": "Deletes one notification. Same ownership check and 404 behavior as Mark read above."
          }
        }
      ]
    },
    {
      "name": "Meetings",
      "description": "Schedule and manage meetings — the same feature as the web app's Meetings page. A meeting creates one task per participant, so these endpoints use the tasks:read / tasks:write OAuth scopes. Access follows the web app, not the workspace-wide default of the rest of this API: you see meetings in projects you created or are a member of (workspace owners and admins see every project), and anything you can't see answers 404. A recurring meeting appears once, carrying its latest occurrence.",
      "item": [
        {
          "name": "GET /meetings",
          "request": {
            "method": "GET",
            "header": [],
            "url": {
              "raw": "{{BASE_URL}}/meetings?limit=25&offset=0",
              "host": [
                "{{BASE_URL}}"
              ],
              "path": [
                "meetings"
              ],
              "query": [
                {
                  "key": "projectId",
                  "value": "42",
                  "description": "Only meetings in this project",
                  "disabled": true
                },
                {
                  "key": "from",
                  "value": "2026-08-01",
                  "description": "YYYY-MM-DD — only meetings whose latest occurrence is on or after this date",
                  "disabled": true
                },
                {
                  "key": "to",
                  "value": "2026-08-31",
                  "description": "YYYY-MM-DD — only meetings whose latest occurrence is on or before this date",
                  "disabled": true
                },
                {
                  "key": "order",
                  "value": "",
                  "description": "asc (default) or desc, by occurrence start. Meetings with no remaining occurrence sort last",
                  "disabled": true
                },
                {
                  "key": "limit",
                  "value": "25",
                  "description": "Page size, default 25, max 100",
                  "disabled": false
                },
                {
                  "key": "offset",
                  "value": "0",
                  "description": "Number of results to skip, default 0",
                  "disabled": false
                }
              ]
            },
            "description": "Lists meetings you can see, ordered by their latest occurrence. Each item carries the occurrence date/time, the participant roster with each participant's task status, the link, location and recurrence rule."
          }
        },
        {
          "name": "GET /meetings/{id}",
          "request": {
            "method": "GET",
            "header": [],
            "url": {
              "raw": "{{BASE_URL}}/meetings/:id",
              "host": [
                "{{BASE_URL}}"
              ],
              "path": [
                "meetings",
                ":id"
              ],
              "variable": [
                {
                  "key": "id",
                  "value": "318",
                  "description": "Example task ID"
                }
              ]
            },
            "description": "Returns one meeting. Readable by members of its project, workspace owners/admins, and leads. 404s — never 403 — for a meeting you can't see, one in another workspace, or an id that isn't a meeting."
          }
        },
        {
          "name": "POST /meetings",
          "request": {
            "method": "POST",
            "header": [
              {
                "key": "Content-Type",
                "value": "application/json"
              }
            ],
            "url": {
              "raw": "{{BASE_URL}}/meetings",
              "host": [
                "{{BASE_URL}}"
              ],
              "path": [
                "meetings"
              ]
            },
            "description": "Schedules a meeting: one task per participant (duplicates removed), each assigned to its participant and notified like any other assignment. You must be a member of the project (403 otherwise; 404 if the project isn't in your workspace), and every participant must be a project member (400). A recurring meeting additionally requires the lead role or a workspace owner/admin role (403 forbidden) and a plan that includes recurring tasks (403 forbidden_plan).",
            "body": {
              "mode": "raw",
              "raw": "{\n  \"projectId\": 42,\n  \"name\": \"Review homepage copy\",\n  \"description\": \"\",\n  \"date\": \"\",\n  \"time\": \"\",\n  \"participantIds\": \"\",\n  \"meetingLink\": null,\n  \"location\": null,\n  \"duration\": null,\n  \"recurrence\": null\n}",
              "options": {
                "raw": {
                  "language": "json"
                }
              }
            }
          }
        },
        {
          "name": "PATCH /meetings/{id}",
          "request": {
            "method": "PATCH",
            "header": [
              {
                "key": "Content-Type",
                "value": "application/json"
              }
            ],
            "url": {
              "raw": "{{BASE_URL}}/meetings/:id",
              "host": [
                "{{BASE_URL}}"
              ],
              "path": [
                "meetings",
                ":id"
              ],
              "variable": [
                {
                  "key": "id",
                  "value": "318",
                  "description": "Example task ID"
                }
              ]
            },
            "description": "Updates the latest occurrence for every participant at once, in a single transaction: reschedule it and/or set the link or location. Send at least one field. Past occurrences of a recurring meeting are left alone. Requires membership of the meeting's project (403 for a lead who can read it but isn't on the project).",
            "body": {
              "mode": "raw",
              "raw": "{\n  \"date\": null,\n  \"time\": null,\n  \"meetingLink\": null,\n  \"location\": null\n}",
              "options": {
                "raw": {
                  "language": "json"
                }
              }
            }
          }
        },
        {
          "name": "DELETE /meetings/{id}",
          "request": {
            "method": "DELETE",
            "header": [],
            "url": {
              "raw": "{{BASE_URL}}/meetings/:id",
              "host": [
                "{{BASE_URL}}"
              ],
              "path": [
                "meetings",
                ":id"
              ],
              "variable": [
                {
                  "key": "id",
                  "value": "318",
                  "description": "Example task ID"
                }
              ]
            },
            "description": "Cancels a meeting: it disappears from lists and a recurring meeting stops generating new occurrences. Tasks already created for participants are kept. Requires the lead role or a workspace owner/admin role (403 otherwise)."
          }
        }
      ]
    },
    {
      "name": "Feature flags",
      "description": "Which optional features are enabled for the caller right now — evaluated through the exact same role, plan, and subscription-status check the server uses to enforce them, so what this reports and what the server actually permits can never disagree.",
      "item": [
        {
          "name": "List feature flags",
          "request": {
            "method": "GET",
            "header": [],
            "url": {
              "raw": "{{BASE_URL}}/feature-flags",
              "host": [
                "{{BASE_URL}}"
              ],
              "path": [
                "feature-flags"
              ]
            },
            "description": "Returns the flag keys enabled for the caller in their active workspace."
          }
        }
      ]
    },
    {
      "name": "Lookups",
      "description": "Small, read-only reference collections for populating a task or project create form — statuses, job-role categories, output types, the workspace roster, and teams. Read-only: authoring these lists stays a Klaarin admin/web-app concern.",
      "item": [
        {
          "name": "List task statuses",
          "request": {
            "method": "GET",
            "header": [],
            "url": {
              "raw": "{{BASE_URL}}/task-statuses?limit=25&offset=0",
              "host": [
                "{{BASE_URL}}"
              ],
              "path": [
                "task-statuses"
              ],
              "query": [
                {
                  "key": "includeInactive",
                  "value": "false",
                  "description": "true to include inactive statuses (active-only by default)",
                  "disabled": true
                },
                {
                  "key": "limit",
                  "value": "25",
                  "description": "Page size, default 25, max 100",
                  "disabled": false
                },
                {
                  "key": "offset",
                  "value": "0",
                  "description": "Number of results to skip, default 0",
                  "disabled": false
                }
              ]
            },
            "description": "Lists the board's task statuses, in board order. Platform-wide — not scoped to a workspace."
          }
        },
        {
          "name": "Get task status",
          "request": {
            "method": "GET",
            "header": [],
            "url": {
              "raw": "{{BASE_URL}}/task-statuses/:id",
              "host": [
                "{{BASE_URL}}"
              ],
              "path": [
                "task-statuses",
                ":id"
              ],
              "variable": [
                {
                  "key": "id",
                  "value": "1",
                  "description": "Example task status ID"
                }
              ]
            },
            "description": "Gets one task status."
          }
        },
        {
          "name": "List project statuses",
          "request": {
            "method": "GET",
            "header": [],
            "url": {
              "raw": "{{BASE_URL}}/project-statuses?limit=25&offset=0",
              "host": [
                "{{BASE_URL}}"
              ],
              "path": [
                "project-statuses"
              ],
              "query": [
                {
                  "key": "includeInactive",
                  "value": "false",
                  "description": "true to include inactive statuses (active-only by default)",
                  "disabled": true
                },
                {
                  "key": "limit",
                  "value": "25",
                  "description": "Page size, default 25, max 100",
                  "disabled": false
                },
                {
                  "key": "offset",
                  "value": "0",
                  "description": "Number of results to skip, default 0",
                  "disabled": false
                }
              ]
            },
            "description": "Lists project statuses, in board order. Platform-wide — not scoped to a workspace."
          }
        },
        {
          "name": "Get project status",
          "request": {
            "method": "GET",
            "header": [],
            "url": {
              "raw": "{{BASE_URL}}/project-statuses/:id",
              "host": [
                "{{BASE_URL}}"
              ],
              "path": [
                "project-statuses",
                ":id"
              ],
              "variable": [
                {
                  "key": "id",
                  "value": "1",
                  "description": "Example project status ID"
                }
              ]
            },
            "description": "Gets one project status."
          }
        },
        {
          "name": "List task categories",
          "request": {
            "method": "GET",
            "header": [],
            "url": {
              "raw": "{{BASE_URL}}/task-categories?limit=25&offset=0",
              "host": [
                "{{BASE_URL}}"
              ],
              "path": [
                "task-categories"
              ],
              "query": [
                {
                  "key": "limit",
                  "value": "25",
                  "description": "Page size, default 25, max 100",
                  "disabled": false
                },
                {
                  "key": "offset",
                  "value": "0",
                  "description": "Number of results to skip, default 0",
                  "disabled": false
                }
              ]
            },
            "description": "Lists the active workspace's task categories (\"job roles\" in the product UI)."
          }
        },
        {
          "name": "Get task category",
          "request": {
            "method": "GET",
            "header": [],
            "url": {
              "raw": "{{BASE_URL}}/task-categories/:id",
              "host": [
                "{{BASE_URL}}"
              ],
              "path": [
                "task-categories",
                ":id"
              ],
              "variable": [
                {
                  "key": "id",
                  "value": "12",
                  "description": "Example task category ID"
                }
              ]
            },
            "description": "Gets one task category."
          }
        },
        {
          "name": "List task types",
          "request": {
            "method": "GET",
            "header": [],
            "url": {
              "raw": "{{BASE_URL}}/task-types?limit=25&offset=0",
              "host": [
                "{{BASE_URL}}"
              ],
              "path": [
                "task-types"
              ],
              "query": [
                {
                  "key": "categoryId",
                  "value": "",
                  "description": "Filter to one category",
                  "disabled": true
                },
                {
                  "key": "limit",
                  "value": "25",
                  "description": "Page size, default 25, max 100",
                  "disabled": false
                },
                {
                  "key": "offset",
                  "value": "0",
                  "description": "Number of results to skip, default 0",
                  "disabled": false
                }
              ]
            },
            "description": "Lists the active workspace's task types (\"outputs\" in the product UI)."
          }
        },
        {
          "name": "Get task type",
          "request": {
            "method": "GET",
            "header": [],
            "url": {
              "raw": "{{BASE_URL}}/task-types/:id",
              "host": [
                "{{BASE_URL}}"
              ],
              "path": [
                "task-types",
                ":id"
              ],
              "variable": [
                {
                  "key": "id",
                  "value": "34",
                  "description": "Example task type ID"
                }
              ]
            },
            "description": "Gets one task type."
          }
        },
        {
          "name": "List users",
          "request": {
            "method": "GET",
            "header": [],
            "url": {
              "raw": "{{BASE_URL}}/users?limit=25&offset=0",
              "host": [
                "{{BASE_URL}}"
              ],
              "path": [
                "users"
              ],
              "query": [
                {
                  "key": "limit",
                  "value": "25",
                  "description": "Page size, default 25, max 100",
                  "disabled": false
                },
                {
                  "key": "offset",
                  "value": "0",
                  "description": "Number of results to skip, default 0",
                  "disabled": false
                }
              ]
            },
            "description": "Lists the active workspace's roster — use it to resolve assignee names to IDs."
          }
        },
        {
          "name": "Get user",
          "request": {
            "method": "GET",
            "header": [],
            "url": {
              "raw": "{{BASE_URL}}/users/:id",
              "host": [
                "{{BASE_URL}}"
              ],
              "path": [
                "users",
                ":id"
              ],
              "variable": [
                {
                  "key": "id",
                  "value": "clx1a2b3c4d5e6f7g8h9i0",
                  "description": "Example user ID"
                }
              ]
            },
            "description": "Gets one user in the active workspace."
          }
        },
        {
          "name": "List teams",
          "request": {
            "method": "GET",
            "header": [],
            "url": {
              "raw": "{{BASE_URL}}/teams?limit=25&offset=0",
              "host": [
                "{{BASE_URL}}"
              ],
              "path": [
                "teams"
              ],
              "query": [
                {
                  "key": "limit",
                  "value": "25",
                  "description": "Page size, default 25, max 100",
                  "disabled": false
                },
                {
                  "key": "offset",
                  "value": "0",
                  "description": "Number of results to skip, default 0",
                  "disabled": false
                }
              ]
            },
            "description": "Lists the active workspace's teams, each with its members and their team-specific role."
          }
        },
        {
          "name": "Get team",
          "request": {
            "method": "GET",
            "header": [],
            "url": {
              "raw": "{{BASE_URL}}/teams/:id",
              "host": [
                "{{BASE_URL}}"
              ],
              "path": [
                "teams",
                ":id"
              ],
              "variable": [
                {
                  "key": "id",
                  "value": "3",
                  "description": "Example team ID"
                }
              ]
            },
            "description": "Gets one team, with its members."
          }
        }
      ]
    },
    {
      "name": "Attachments",
      "description": "Upload files to a task via a presign → confirm flow (two requests, no chunking): presign for a direct-upload URL, upload the bytes yourself, then confirm to record the attachment. An attachment on a task the caller can't see 404s like the task itself.",
      "item": [
        {
          "name": "List attachments",
          "request": {
            "method": "GET",
            "header": [],
            "url": {
              "raw": "{{BASE_URL}}/tasks/:id/attachments?limit=25&offset=0",
              "host": [
                "{{BASE_URL}}"
              ],
              "path": [
                "tasks",
                ":id",
                "attachments"
              ],
              "query": [
                {
                  "key": "limit",
                  "value": "25",
                  "description": "Page size, default 25, max 100",
                  "disabled": false
                },
                {
                  "key": "offset",
                  "value": "0",
                  "description": "Number of results to skip, default 0",
                  "disabled": false
                }
              ],
              "variable": [
                {
                  "key": "id",
                  "value": "318",
                  "description": "Example task ID"
                }
              ]
            },
            "description": "Lists the latest version of each attachment on a task."
          }
        },
        {
          "name": "Presign attachment upload",
          "request": {
            "method": "POST",
            "header": [
              {
                "key": "Content-Type",
                "value": "application/json"
              }
            ],
            "url": {
              "raw": "{{BASE_URL}}/tasks/:id/attachments/presign",
              "host": [
                "{{BASE_URL}}"
              ],
              "path": [
                "tasks",
                ":id",
                "attachments",
                "presign"
              ],
              "variable": [
                {
                  "key": "id",
                  "value": "318",
                  "description": "Example task ID"
                }
              ]
            },
            "description": "Requests a direct-upload URL for one file. Also checked here: the workspace's storage-plan limit, using the fileSize you declare — rejected with 403 forbidden_plan before you waste an upload.",
            "body": {
              "mode": "raw",
              "raw": "{\n  \"fileName\": \"mockup.png\",\n  \"fileType\": \"image/png\",\n  \"fileSize\": 482913\n}",
              "options": {
                "raw": {
                  "language": "json"
                }
              }
            }
          }
        },
        {
          "name": "Confirm attachment upload",
          "request": {
            "method": "POST",
            "header": [
              {
                "key": "Content-Type",
                "value": "application/json"
              }
            ],
            "url": {
              "raw": "{{BASE_URL}}/tasks/:id/attachments/confirm",
              "host": [
                "{{BASE_URL}}"
              ],
              "path": [
                "tasks",
                ":id",
                "attachments",
                "confirm"
              ],
              "variable": [
                {
                  "key": "id",
                  "value": "318",
                  "description": "Example task ID"
                }
              ]
            },
            "description": "Records the attachment after you've uploaded the bytes to the presigned URL. Returns the created attachment.",
            "body": {
              "mode": "raw",
              "raw": "{\n  \"fileName\": \"mockup.png\",\n  \"fileType\": \"image/png\",\n  \"fileSize\": 482913,\n  \"s3Key\": \"tasks/318/1785400000000-mockup.png\"\n}",
              "options": {
                "raw": {
                  "language": "json"
                }
              }
            }
          }
        },
        {
          "name": "GET /projects/{id}/attachments",
          "request": {
            "method": "GET",
            "header": [],
            "url": {
              "raw": "{{BASE_URL}}/projects/:id/attachments?limit=25&offset=0",
              "host": [
                "{{BASE_URL}}"
              ],
              "path": [
                "projects",
                ":id",
                "attachments"
              ],
              "query": [
                {
                  "key": "limit",
                  "value": "25",
                  "description": "Page size, default 25, max 100",
                  "disabled": false
                },
                {
                  "key": "offset",
                  "value": "0",
                  "description": "Number of results to skip, default 0",
                  "disabled": false
                }
              ],
              "variable": [
                {
                  "key": "id",
                  "value": "42",
                  "description": "Example project ID"
                }
              ]
            },
            "description": "Lists a project's attachments, newest first."
          }
        },
        {
          "name": "GET /attachments/{id}/download",
          "request": {
            "method": "GET",
            "header": [],
            "url": {
              "raw": "{{BASE_URL}}/attachments/:id/download",
              "host": [
                "{{BASE_URL}}"
              ],
              "path": [
                "attachments",
                ":id",
                "download"
              ],
              "variable": [
                {
                  "key": "id",
                  "value": "318",
                  "description": "Example task ID"
                }
              ]
            },
            "description": "Generates a presigned download URL for one task attachment by id."
          }
        }
      ]
    },
    {
      "name": "Chunked uploads",
      "description": "Upload large files in chunks via a five-step session — init, upload each chunk, then complete (or abort) — for files too big for the single-request presign → confirm flow in Attachments. A session belongs to one task or one project, is owned by the caller who started it (status/complete/abort all 404 for anyone else), and expires 24 hours after init. The workspace's storage-plan limit is checked once, at init, using the file size you declare.",
      "item": [
        {
          "name": "POST /chunked-upload/init",
          "request": {
            "method": "POST",
            "header": [
              {
                "key": "Content-Type",
                "value": "application/json"
              }
            ],
            "url": {
              "raw": "{{BASE_URL}}/chunked-upload/init",
              "host": [
                "{{BASE_URL}}"
              ],
              "path": [
                "chunked-upload",
                "init"
              ]
            },
            "description": "Starts a new upload session. Exactly one of taskId/projectId targets where the resulting attachment is created; if both are given, taskId wins.",
            "body": {
              "mode": "raw",
              "raw": "{\n  \"taskId\": 318,\n  \"projectId\": 42,\n  \"fileName\": \"mockup.png\",\n  \"fileSize\": 482913,\n  \"fileType\": \"image/png\",\n  \"parentId\": 0\n}",
              "options": {
                "raw": {
                  "language": "json"
                }
              }
            }
          }
        },
        {
          "name": "GET /chunked-upload/status/{sessionId}",
          "request": {
            "method": "GET",
            "header": [],
            "url": {
              "raw": "{{BASE_URL}}/chunked-upload/status/:sessionId",
              "host": [
                "{{BASE_URL}}"
              ],
              "path": [
                "chunked-upload",
                "status",
                ":sessionId"
              ],
              "variable": [
                {
                  "key": "sessionId",
                  "value": "318",
                  "description": "Example task ID"
                }
              ]
            },
            "description": "Returns the progress of an upload session."
          }
        },
        {
          "name": "POST /chunked-upload/chunk/{sessionId}/{partNumber}",
          "request": {
            "method": "POST",
            "header": [],
            "url": {
              "raw": "{{BASE_URL}}/chunked-upload/chunk/:sessionId/:partNumber",
              "host": [
                "{{BASE_URL}}"
              ],
              "path": [
                "chunked-upload",
                "chunk",
                ":sessionId",
                ":partNumber"
              ],
              "variable": [
                {
                  "key": "sessionId",
                  "value": "318",
                  "description": "Example task ID"
                },
                {
                  "key": "partNumber",
                  "value": "318",
                  "description": "Example task ID"
                }
              ]
            },
            "description": "Uploads one chunk's raw bytes as a multipart part. The request body is the chunk itself (application/octet-stream), not JSON — there are no body parameters to set."
          }
        },
        {
          "name": "POST /chunked-upload/complete/{sessionId}",
          "request": {
            "method": "POST",
            "header": [],
            "url": {
              "raw": "{{BASE_URL}}/chunked-upload/complete/:sessionId",
              "host": [
                "{{BASE_URL}}"
              ],
              "path": [
                "chunked-upload",
                "complete",
                ":sessionId"
              ],
              "variable": [
                {
                  "key": "sessionId",
                  "value": "318",
                  "description": "Example task ID"
                }
              ]
            },
            "description": "Finalizes the session once every chunk has arrived and creates the resulting attachment. Takes no request body."
          }
        },
        {
          "name": "DELETE /chunked-upload/abort/{sessionId}",
          "request": {
            "method": "DELETE",
            "header": [],
            "url": {
              "raw": "{{BASE_URL}}/chunked-upload/abort/:sessionId",
              "host": [
                "{{BASE_URL}}"
              ],
              "path": [
                "chunked-upload",
                "abort",
                ":sessionId"
              ],
              "variable": [
                {
                  "key": "sessionId",
                  "value": "318",
                  "description": "Example task ID"
                }
              ]
            },
            "description": "Cancels an in-progress session."
          }
        }
      ]
    }
  ]
}
