{
  "info": {
    "_postman_id": "workel-public-api-v1",
    "name": "Workel Public API",
    "description": "Workspace-scoped REST surface at {{baseUrl}}.\n\n**Setup**: import the companion environment (workel-public-api.postman_environment.json), paste your `wk_…` key into `apiKey` (create one in Workel → Settings → Developers), then run **Meta → Confirm a key is working** first — it fills nothing but proves auth. Next run **Projects → List visible projects** and copy an id into `projectId`.\n\nAuth is collection-level Bearer {{apiKey}} — every request inherits it. POSTs carry a generated Idempotency-Key ({{$guid}}) so re-sends are safe.\n\nDocs: https://developer.workel.com · Full reference: https://developer.workel.com/reference.html",
    "schema": "https://schema.getpostman.com/json/collection/v2.1.0/collection.json"
  },
  "auth": {
    "type": "bearer",
    "bearer": [
      {
        "key": "token",
        "value": "{{apiKey}}",
        "type": "string"
      }
    ]
  },
  "item": [
    {
      "name": "Meta",
      "description": "Key/workspace introspection.",
      "item": [
        {
          "name": "Confirm a key is working and inspect its scopes and rate-limit state",
          "request": {
            "method": "GET",
            "header": [],
            "url": {
              "raw": "{{baseUrl}}/me",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "me"
              ]
            },
            "description": "Requires a valid key but no specific scope.\n\nScope: see the Developers guide — https://developer.workel.com"
          },
          "event": [
            {
              "listen": "test",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "pm.test(`status is not 5xx (${pm.response.code})`, () => pm.expect(pm.response.code).to.be.below(500));",
                  "pm.test('X-Request-Id present', () => pm.expect(pm.response.headers.has('X-Request-Id')).to.be.true);"
                ]
              }
            }
          ]
        }
      ]
    },
    {
      "name": "Projects",
      "description": "Read-only. Never includes inbox, archived, or private projects.",
      "item": [
        {
          "name": "List visible projects",
          "request": {
            "method": "GET",
            "header": [],
            "url": {
              "raw": "{{baseUrl}}/projects?limit=&cursor=",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "projects"
              ],
              "query": [
                {
                  "key": "limit",
                  "value": "",
                  "description": "Page size. Values ≤ 0 fall back to the default; values above the maximum are silently clamped, never rejected with a 422. Default 25, maximum 100 — both launch placeholders pending ratification, not a contract value.",
                  "disabled": true
                },
                {
                  "key": "cursor",
                  "value": "",
                  "description": "Opaque pagination cursor, taken verbatim from a previous response's `meta.next_cursor`. Never construct one by hand.",
                  "disabled": true
                }
              ]
            },
            "description": "Excludes inbox projects, archived projects, and private projects.\n\nScope: see the Developers guide — https://developer.workel.com"
          },
          "event": [
            {
              "listen": "test",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "pm.test(`status is not 5xx (${pm.response.code})`, () => pm.expect(pm.response.code).to.be.below(500));",
                  "pm.test('X-Request-Id present', () => pm.expect(pm.response.headers.has('X-Request-Id')).to.be.true);"
                ]
              }
            }
          ]
        },
        {
          "name": "Fetch a single visible project",
          "request": {
            "method": "GET",
            "header": [],
            "url": {
              "raw": "{{baseUrl}}/projects/{id}",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "projects",
                "{{projectId}}"
              ]
            },
            "description": "Scope: see the Developers guide — https://developer.workel.com"
          },
          "event": [
            {
              "listen": "test",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "pm.test(`status is not 5xx (${pm.response.code})`, () => pm.expect(pm.response.code).to.be.below(500));",
                  "pm.test('X-Request-Id present', () => pm.expect(pm.response.headers.has('X-Request-Id')).to.be.true);"
                ]
              }
            }
          ]
        },
        {
          "name": "List a project's board columns",
          "request": {
            "method": "GET",
            "header": [],
            "url": {
              "raw": "{{baseUrl}}/projects/{id}/cards?limit=&cursor=",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "projects",
                "{{projectId}}",
                "cards"
              ],
              "query": [
                {
                  "key": "limit",
                  "value": "",
                  "description": "Page size. Values ≤ 0 fall back to the default; values above the maximum are silently clamped, never rejected with a 422. Default 25, maximum 100 — both launch placeholders pending ratification, not a contract value.",
                  "disabled": true
                },
                {
                  "key": "cursor",
                  "value": "",
                  "description": "Opaque pagination cursor, taken verbatim from a previous response's `meta.next_cursor`. Never construct one by hand.",
                  "disabled": true
                }
              ]
            },
            "description": "A Workel \"card\" here is a board COLUMN (e.g. \"To Do\"/\"In Progress\"), not a task — so integrators can target a column id on task create.\n\nScope: see the Developers guide — https://developer.workel.com"
          },
          "event": [
            {
              "listen": "test",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "pm.test(`status is not 5xx (${pm.response.code})`, () => pm.expect(pm.response.code).to.be.below(500));",
                  "pm.test('X-Request-Id present', () => pm.expect(pm.response.headers.has('X-Request-Id')).to.be.true);"
                ]
              }
            }
          ]
        }
      ]
    },
    {
      "name": "Members",
      "description": "Active workspace members. The one endpoint on this surface that exposes email addresses.",
      "item": [
        {
          "name": "List active workspace members",
          "request": {
            "method": "GET",
            "header": [],
            "url": {
              "raw": "{{baseUrl}}/members?limit=&cursor=",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "members"
              ],
              "query": [
                {
                  "key": "limit",
                  "value": "",
                  "description": "Page size. Values ≤ 0 fall back to the default; values above the maximum are silently clamped, never rejected with a 422. Default 25, maximum 100 — both launch placeholders pending ratification, not a contract value.",
                  "disabled": true
                },
                {
                  "key": "cursor",
                  "value": "",
                  "description": "Opaque pagination cursor, taken verbatim from a previous response's `meta.next_cursor`. Never construct one by hand.",
                  "disabled": true
                }
              ]
            },
            "description": "Only members with an active (not pending/suspended) membership are listed.\n\nScope: see the Developers guide — https://developer.workel.com"
          },
          "event": [
            {
              "listen": "test",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "pm.test(`status is not 5xx (${pm.response.code})`, () => pm.expect(pm.response.code).to.be.below(500));",
                  "pm.test('X-Request-Id present', () => pm.expect(pm.response.headers.has('X-Request-Id')).to.be.true);"
                ]
              }
            }
          ]
        }
      ]
    },
    {
      "name": "Tasks",
      "description": "Read and write. Reachable only through a visible project's board.",
      "item": [
        {
          "name": "List tasks across every visible project",
          "request": {
            "method": "GET",
            "header": [],
            "url": {
              "raw": "{{baseUrl}}/tasks?limit=&cursor=&project_id=&card_id=&completed=&due_before=&due_after=&updated_since=",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "tasks"
              ],
              "query": [
                {
                  "key": "limit",
                  "value": "",
                  "description": "Page size. Values ≤ 0 fall back to the default; values above the maximum are silently clamped, never rejected with a 422. Default 25, maximum 100 — both launch placeholders pending ratification, not a contract value.",
                  "disabled": true
                },
                {
                  "key": "cursor",
                  "value": "",
                  "description": "Opaque pagination cursor, taken verbatim from a previous response's `meta.next_cursor`. Never construct one by hand.",
                  "disabled": true
                },
                {
                  "key": "project_id",
                  "value": "",
                  "description": "Narrow to one project. A foreign/private/inbox/nonexistent id simply yields zero rows.",
                  "disabled": true
                },
                {
                  "key": "card_id",
                  "value": "",
                  "description": "Narrow to one board column.",
                  "disabled": true
                },
                {
                  "key": "completed",
                  "value": "",
                  "description": "`true` for progress===100, `false` for progress<100 or null (never-progressed). Any other value is silently ignored — the filter is simply not applied, never a 422.",
                  "disabled": true
                },
                {
                  "key": "due_before",
                  "value": "",
                  "description": "Strict `YYYY-MM-DD`. A value not shaped like this is silently ignored.",
                  "disabled": true
                },
                {
                  "key": "due_after",
                  "value": "",
                  "description": "Strict `YYYY-MM-DD`. A value not shaped like this is silently ignored.",
                  "disabled": true
                },
                {
                  "key": "updated_since",
                  "value": "",
                  "description": "Any parseable date/time string. Unparseable values are silently ignored.",
                  "disabled": true
                }
              ]
            },
            "description": "Scope: see the Developers guide — https://developer.workel.com"
          },
          "event": [
            {
              "listen": "test",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "pm.test(`status is not 5xx (${pm.response.code})`, () => pm.expect(pm.response.code).to.be.below(500));",
                  "pm.test('X-Request-Id present', () => pm.expect(pm.response.headers.has('X-Request-Id')).to.be.true);"
                ]
              }
            }
          ]
        },
        {
          "name": "Create a task",
          "request": {
            "method": "POST",
            "header": [
              {
                "key": "Content-Type",
                "value": "application/json"
              },
              {
                "key": "Idempotency-Key",
                "value": "{{$guid}}",
                "description": "Safe-retry key, scoped to your API key. Same key + same body replays the stored response; same key + different body → 409."
              }
            ],
            "body": {
              "mode": "raw",
              "raw": "{\n  \"project_id\": \"{{projectId}}\",\n  \"title_text\": \"New order #4821\"\n}",
              "options": {
                "raw": {
                  "language": "json"
                }
              }
            },
            "url": {
              "raw": "{{baseUrl}}/tasks",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "tasks"
              ]
            },
            "description": "Runs the full task-create recipe (tenant re-check, permission check against the target project, default-column resolution, assignee membership re-verification, activity/notification dispatch, realtime broadcast) — not a thinned-down write.\n\nScope: see the Developers guide — https://developer.workel.com"
          },
          "event": [
            {
              "listen": "test",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "pm.test(`status is not 5xx (${pm.response.code})`, () => pm.expect(pm.response.code).to.be.below(500));",
                  "pm.test('X-Request-Id present', () => pm.expect(pm.response.headers.has('X-Request-Id')).to.be.true);"
                ]
              }
            }
          ]
        },
        {
          "name": "Fetch a single task",
          "request": {
            "method": "GET",
            "header": [],
            "url": {
              "raw": "{{baseUrl}}/tasks/{id}",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "tasks",
                "{{taskId}}"
              ]
            },
            "description": "Scope: see the Developers guide — https://developer.workel.com"
          },
          "event": [
            {
              "listen": "test",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "pm.test(`status is not 5xx (${pm.response.code})`, () => pm.expect(pm.response.code).to.be.below(500));",
                  "pm.test('X-Request-Id present', () => pm.expect(pm.response.headers.has('X-Request-Id')).to.be.true);"
                ]
              }
            }
          ]
        },
        {
          "name": "Update a task",
          "request": {
            "method": "PATCH",
            "header": [
              {
                "key": "Content-Type",
                "value": "application/json"
              }
            ],
            "body": {
              "mode": "raw",
              "raw": "{\n  \"title_text\": \"New order #4821\",\n  \"description\": \"Created from the website order form.\",\n  \"priority\": \"low\"\n}",
              "options": {
                "raw": {
                  "language": "json"
                }
              }
            },
            "url": {
              "raw": "{{baseUrl}}/tasks/{id}",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "tasks",
                "{{taskId}}"
              ]
            },
            "description": "NOT idempotency-key aware (`PublicApiIdempotency` only inspects `POST` requests) — a retried PATCH with the same body is safe only because the writable fields are themselves idempotent, not because of Idempotency-Key support. Every accepted field is applied through the same `TaskUpdateService` the web UI's task-update path uses, so the reminder re-arm, orphaned-`end_time` cleanup, and recurring-task roll-forward invariants apply identically here.\n\nScope: see the Developers guide — https://developer.workel.com"
          },
          "event": [
            {
              "listen": "test",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "pm.test(`status is not 5xx (${pm.response.code})`, () => pm.expect(pm.response.code).to.be.below(500));",
                  "pm.test('X-Request-Id present', () => pm.expect(pm.response.headers.has('X-Request-Id')).to.be.true);"
                ]
              }
            }
          ]
        }
      ]
    },
    {
      "name": "Task comments",
      "description": "Read and write. Plain text only — no @-mention syntax in v1.",
      "item": [
        {
          "name": "List a task's comments",
          "request": {
            "method": "GET",
            "header": [],
            "url": {
              "raw": "{{baseUrl}}/tasks/{id}/comments?limit=&cursor=",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "tasks",
                "{{taskId}}",
                "comments"
              ],
              "query": [
                {
                  "key": "limit",
                  "value": "",
                  "description": "Page size. Values ≤ 0 fall back to the default; values above the maximum are silently clamped, never rejected with a 422. Default 25, maximum 100 — both launch placeholders pending ratification, not a contract value.",
                  "disabled": true
                },
                {
                  "key": "cursor",
                  "value": "",
                  "description": "Opaque pagination cursor, taken verbatim from a previous response's `meta.next_cursor`. Never construct one by hand.",
                  "disabled": true
                }
              ]
            },
            "description": "Every comment on the task, top-level and replies alike, oldest first. Exists mainly so a `comment.created` webhook consumer (a thin, ids-only payload) can fetch the comment it was told about.\n\nScope: see the Developers guide — https://developer.workel.com"
          },
          "event": [
            {
              "listen": "test",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "pm.test(`status is not 5xx (${pm.response.code})`, () => pm.expect(pm.response.code).to.be.below(500));",
                  "pm.test('X-Request-Id present', () => pm.expect(pm.response.headers.has('X-Request-Id')).to.be.true);"
                ]
              }
            }
          ]
        },
        {
          "name": "Comment on a task",
          "request": {
            "method": "POST",
            "header": [
              {
                "key": "Content-Type",
                "value": "application/json"
              },
              {
                "key": "Idempotency-Key",
                "value": "{{$guid}}",
                "description": "Safe-retry key, scoped to your API key. Same key + same body replays the stored response; same key + different body → 409."
              }
            ],
            "body": {
              "mode": "raw",
              "raw": "{\n  \"body\": \"Created from the website order form.\"\n}",
              "options": {
                "raw": {
                  "language": "json"
                }
              }
            },
            "url": {
              "raw": "{{baseUrl}}/tasks/{id}/comments",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "tasks",
                "{{taskId}}",
                "comments"
              ]
            },
            "description": "Scope: see the Developers guide — https://developer.workel.com"
          },
          "event": [
            {
              "listen": "test",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "pm.test(`status is not 5xx (${pm.response.code})`, () => pm.expect(pm.response.code).to.be.below(500));",
                  "pm.test('X-Request-Id present', () => pm.expect(pm.response.headers.has('X-Request-Id')).to.be.true);"
                ]
              }
            }
          ]
        }
      ]
    },
    {
      "name": "Events",
      "description": "Read and write. Workspace-level and project-level calendar events.",
      "item": [
        {
          "name": "List workspace and project events",
          "request": {
            "method": "GET",
            "header": [],
            "url": {
              "raw": "{{baseUrl}}/events?limit=&cursor=&from=&to=",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "events"
              ],
              "query": [
                {
                  "key": "limit",
                  "value": "",
                  "description": "Page size. Values ≤ 0 fall back to the default; values above the maximum are silently clamped, never rejected with a 422. Default 25, maximum 100 — both launch placeholders pending ratification, not a contract value.",
                  "disabled": true
                },
                {
                  "key": "cursor",
                  "value": "",
                  "description": "Opaque pagination cursor, taken verbatim from a previous response's `meta.next_cursor`. Never construct one by hand.",
                  "disabled": true
                },
                {
                  "key": "from",
                  "value": "",
                  "description": "Inclusive lower bound. Any parseable date. Unparseable → 422 `invalid_date`.",
                  "disabled": true
                },
                {
                  "key": "to",
                  "value": "",
                  "description": "Inclusive upper bound. Any parseable date. Unparseable → 422 `invalid_date`. Combined with `from`, the window may not exceed 400 days — a wider window is a hard 422 `window_too_large`, never silently clamped.",
                  "disabled": true
                }
              ]
            },
            "description": "Includes events bound directly to the workspace and events on any visible project.\n\nScope: see the Developers guide — https://developer.workel.com"
          },
          "event": [
            {
              "listen": "test",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "pm.test(`status is not 5xx (${pm.response.code})`, () => pm.expect(pm.response.code).to.be.below(500));",
                  "pm.test('X-Request-Id present', () => pm.expect(pm.response.headers.has('X-Request-Id')).to.be.true);"
                ]
              }
            }
          ]
        },
        {
          "name": "Create an event",
          "request": {
            "method": "POST",
            "header": [
              {
                "key": "Content-Type",
                "value": "application/json"
              },
              {
                "key": "Idempotency-Key",
                "value": "{{$guid}}",
                "description": "Safe-retry key, scoped to your API key. Same key + same body replays the stored response; same key + different body → 409."
              }
            ],
            "body": {
              "mode": "raw",
              "raw": "{\n  \"title\": \"New order #4821\",\n  \"date\": \"2026-08-30\",\n  \"start_time\": \"14:30\",\n  \"end_time\": \"14:30\",\n  \"repeat\": \"none\",\n  \"project_id\": \"{{projectId}}\"\n}",
              "options": {
                "raw": {
                  "language": "json"
                }
              }
            },
            "url": {
              "raw": "{{baseUrl}}/events",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "events"
              ]
            },
            "description": "Unconditionally logged to the workspace/project activity feed on success, even with zero invitees — an API-originated write is exactly the case this audit trail exists for.\n\nScope: see the Developers guide — https://developer.workel.com"
          },
          "event": [
            {
              "listen": "test",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "pm.test(`status is not 5xx (${pm.response.code})`, () => pm.expect(pm.response.code).to.be.below(500));",
                  "pm.test('X-Request-Id present', () => pm.expect(pm.response.headers.has('X-Request-Id')).to.be.true);"
                ]
              }
            }
          ]
        }
      ]
    }
  ],
  "variable": []
}