Tasks: boards and cards of the shop's own work
/docs/tasksdraftEN· проверено 2026-09-24
Резюме по-русски. Задачи магазина — доски задач (колонки) и карточки на них: название, описание, исполнитель, срок, метки, приоритет, чек-лист, ссылки. Это рабочий инструмент команды, а не заявки покупателей: праваtasks:read/tasks:write, заявки ими не открываются и наоборот; колонки и саму доску задач правит тот жеtasks:write(с 2026-09-24, W4). Решения владельца 2026-09-24: доски задач видят все участники магазина, непустую доску удалить нельзя, правила переходов досок заявок к задачам не применяются.
Status: draft · Verified: 2026-09-24, integration tests on PostgreSQL (TestTask*, handlers and PAT gates) and a wire probe of the real HTTP gateway (§7) · Owner: CRM line (backend-3D#15) Serves: GET /docs/tasks
1. What you can do here
Keep the shop's own to-do list through a token: list the task boards and their columns, create a board, create, read, edit, move and delete task cards, filter them by board, column, assignee or overdue. A card carries a title, a description, an assignee (a shop member by id or any name as text), a due date and an extra object with labels, priority, a checklist and links — the Trello vocabulary on purpose.
The OpenAPI of this area alone (eight task operations, nine shared board and column operations and the key passport, about 42 KB) is GET /openapi.json?scope=tasks; every scope and its operations is GET /docs/scopes.json.
2. Decide first (forks)
| If you want… | Take this path | Cost |
|---|---|---|
| to track the team's work | tasks — /task-boards, /tasks with tasks:* | no personal data, not tied to buyers |
| to process buyer requests from a form | leads — /sandboxes, /form-leads with forms:* / leads:* (skill forms) | personal data: owner-issued sensitive scopes, dev keys read nothing |
| to add, rename, reorder or delete a column, rename or delete a board | the board endpoints shared with leads: /sandboxes/{id}, /sandboxes/{sandbox_id}/columns, /sandbox-columns/{id} | `tasks:write` on a task board; the same endpoints on a leads board need forms:write and refuse a tasks key |
| to assign a task to a person | assignee_user_id if you already know a member id (seen on an existing card or given by the owner); otherwise assignee_name as text | a token cannot list shop members; an unknown id is refused, a name is never checked |
A task board is a board with view: "tasks". It never appears in the leads list (GET /sandboxes without view), a form never drops a lead onto it, and a task is never created on a leads board (TASK_BOARD_MISMATCH).
3. Objects, endpoints and the wire
Board — the same object as a leads board (Sandbox): id, name, view: "tasks", columns[] (id, label, kind: intake|normal, sort_order, settings). A new board gets three columns: the intake (kind: intake) and two normal ones, labelled in Russian — «К выполнению · В работе · Готово» — whatever the shop language; rename them with PUT /sandbox-columns/{id} (tasks:write). A new shop has no task board: GET /task-boards answering {"result": []} is normal, create one.
Board and column endpoints are shared with leads boards. The right is chosen by the kind of the board they touch: a task board needs tasks:*, a leads board forms:*. A key with only forms:write is refused on a task board, a key with only tasks:write on a leads board — 403 PAT_SCOPE_MISSING naming the scope that board needs.
| Method and path | Scope on a task board | What it does |
|---|---|---|
GET /sandboxes?view=tasks | tasks:read | the same list as GET /task-boards; without view it is the leads list (forms:read) |
POST /sandboxes {name, view: "tasks"} | tasks:write | the same as POST /task-boards |
PUT /sandboxes/{id} {name} · DELETE /sandboxes/{id} {} | tasks:write | rename · delete an empty board |
POST /sandboxes/reorder {ids} | tasks:write | board order; a list mixing both kinds needs both rights |
POST /sandboxes/{sandbox_id}/columns {label} | tasks:write | a normal column at the end |
PUT /sandbox-columns/{id} {label} · DELETE /sandbox-columns/{id} {} | tasks:write | rename · delete (its cards go to the intake; the intake is protected) |
POST /sandboxes/{sandbox_id}/columns/reorder {ids} | tasks:write | column order, the full list |
Task — id, sandbox_id, column_id, title, description, position (dense order inside the column, 0..n-1), assignee_user_id (0 — none), assignee_name, due_at, extra, created_at, updated_at, created_by, updated_by (0 — unknown; a token write records the user who issued the key).
| Method and path | Scope | What it does |
|---|---|---|
GET /task-boards | tasks:read | task boards with their columns |
POST /task-boards {name} | tasks:write | new board with the three starter columns |
GET /tasks?sandbox_id&column_id&assignee_user_id&overdue | tasks:read | tasks by filter, ordered column_id, position, id; 0 / false — no filter |
GET /tasks/{id} | tasks:read | one task |
POST /tasks | tasks:write | new task at the end of a column; column_id: 0 — the board's intake |
PATCH /tasks/{id} | tasks:write | partial edit of fields; never the column or the order |
PUT /tasks/{id}/position {column_id, position} | tasks:write | move inside the column or to another column of the same board |
DELETE /tasks/{id} with body {} | tasks:write | hard delete; the column is renumbered |
Every task operation is private to the token's shop: another shop's task answers exactly like a missing one. Reads work for a dev-contour key too; writes are live operations, so a dev key gets PAT_CONTOUR_MISMATCH — tasks have no draft. capabilities.tasks_read / tasks_write in GET /v1/account/token say what this key may call.
Wire gotchas
- 64-bit ids are JSON strings on output:
"id": "42","sandbox_id": "7","column_id": "19". Input accepts both19and"19". 32-bit fields (position,assignee_user_id,created_by) are numbers. - `extra` is base64 of a raw JSON object, both ways — it is a
bytesfield. Send"extra": "eyJwcmlvcml0eSI6ImhpZ2gifQ=="for{"priority":"high"}. A JSON object in its place is refused with400 INVALID_REQUEST. An empty card reads"extra": "e30="({}). - `column_id: 0` means the intake of the board on create. On move
column_idis required (≥ 1). - `due_at` is an RFC3339 string,
""means no due date. Any offset is accepted; it is returned in UTC (2026-10-01T18:00:00+03:00reads back as2026-10-01T15:00:00Z). Anything else isTASK_BAD_DUE_AT. - `PATCH` knows presence. A field you do not send is left alone.
assignee_user_id: 0removes the member,due_at: ""removes the due date,assignee_name: ""clears the name.extra: absent or""— unchanged,"e30="({}) — reset. - Unknown body fields are refused, not ignored:
PATCHwithcolumn_idanswers400 invalid request body: column_id— moving isPUT /tasks/{id}/position. - `DELETE` needs a body:
-d '{}'. Without it the gateway answers400 invalid request body. extrais stored normalised: empty lists dropped, key order not kept (the column is JSONB). Compare meaning, not bytes.
extra whitelist
{
"labels": ["seo", "urgent-fix"],
"priority": "high",
"checklist": [{"text": "Write the brief", "done": true}],
"links": [{"title": "Spec", "url": "https://example.com/spec"}]
}Any other key, at any depth, is refused with TASK_BAD_EXTRA and the reason after the code. A link must start with http:// or https://. Limits: §6.
Refusals
| Code | HTTP | When |
|---|---|---|
TASK_NOT_FOUND | 404 | the task does not exist or belongs to another shop |
SANDBOX_NOT_FOUND | 404 | POST /tasks with an unknown or foreign sandbox_id |
TASK_BOARD_MISMATCH | 400 | the board is a leads board, or the column belongs to another board |
TASK_BAD_EXTRA | 400 | extra outside the whitelist or its limits; the reason follows the code |
TASK_BAD_ASSIGNEE | 400 | assignee_user_id is not a member of the shop |
TASK_BAD_DUE_AT | 400 | due_at is not RFC3339 |
SANDBOX_NOT_EMPTY | 400 | DELETE /sandboxes/{id} of a task board that still has tasks |
INVALID_REQUEST | 400 | the body does not parse: unknown field, extra not base64, no body on DELETE |
PAT_SCOPE_MISSING | 403 | the key lacks tasks:read / tasks:write, or a board endpoint touched a board of the other kind (the required scope is in the text) |
PAT_CONTOUR_MISMATCH | 403 | a dev-contour key tried to write |
Proto validation (empty title, position < 0, over-long strings) answers 400 with the field name in the text.
Webhooks
task.created, task.updated (fields), task.moved (column or position, plus from_column_id) and task.deleted (the card before deletion) carry the card without description; ids are numbers there and extra is a JSON object. Any shop member may subscribe in the cabinet — class operational (/docs/webhooks §6).
4. Recipes
$API is the API base, $T is Bearer vz_pat_….
4.1 Find or create a board.
curl -s "$API/task-boards" -H "Authorization: $T"
curl -s -X POST "$API/task-boards" -H "Authorization: $T" -d '{"name":"Launch"}'Verify: the board is in GET /task-boards with view: "tasks" and three columns, the first with kind: "intake".
4.2 Create a task with a checklist.
EXTRA=$(printf '%s' '{"priority":"high","checklist":[{"text":"Draft","done":false}]}' | base64 | tr -d '\n')
curl -s -X POST "$API/tasks" -H "Authorization: $T" -d '{
"sandbox_id": "7", "column_id": 0, "title": "Prepare the autumn banner",
"assignee_name": "Anna (designer)", "due_at": "2026-10-01T18:00:00Z",
"extra": "'"$EXTRA"'"}'Verify: GET /tasks/{id} returns the card in the intake column with the last position; jq -r .result.extra | base64 -d prints your object.
4.3 Move a card to "in progress", on top.
curl -s -X PUT "$API/tasks/42/position" -H "Authorization: $T" -d '{"column_id":"19","position":0}'Verify: GET /tasks?column_id=19 lists it first; the source column is renumbered without a gap.
4.4 Tick a checklist item. extra is replaced whole: read the task, decode extra, change the item, encode, PATCH /tasks/{id} with the new extra. Verify by reading it back.
4.5 Overdue work of one person. GET /tasks?assignee_user_id=12&overdue=true — then drop the cards that sit in your "done" column yourself (§5).
4.6 Delete. curl -s -X DELETE "$API/tasks/42" -H "Authorization: $T" -d '{}'. Verify: GET /tasks/42 answers TASK_NOT_FOUND.
5. Silently ignored
- Unknown query parameters of `GET /tasks`. A misspelled filter (
board_id=7,page.limit=5) is dropped and the answer is every task of the shop with a 200. The filters are exactlysandbox_id,column_id,assignee_user_id,overdue. Compare the count before and after a filter. - No pagination.
GET /tasksreturns the whole filtered list;page.*parameters do nothing. - Lead-board rules do not apply to tasks. A task board column can store
settings(stage,no_return,require_comment,limit,deadline_days) and the board amodethrough the leads endpoints; for tasks they are stored and have no effect — any card moves to any column of its board. - `overdue=true` ignores the column. It is
due_at < now(), so a finished card in "Готово" with a past due date is still overdue. - `position` past the end puts the card at the tail. The actual position is in
result.position. - `extra: ""` on `PATCH` changes nothing; a reset is
{}("e30="). - `assignee_name` is never checked and survives removing
assignee_user_id: both are independent fields.
6. Limits
{
"title_max_chars": 255,
"description_max_chars": 20000,
"assignee_name_max_chars": 255,
"board_name_max_chars": 255,
"extra_max_bytes": 16384,
"extra_keys": ["labels", "priority", "checklist", "links"],
"labels_max": 10,
"label_max_chars": 32,
"priority": ["low", "normal", "high", "urgent"],
"checklist_max": 50,
"checklist_text_max_chars": 200,
"links_max": 20,
"link_title_max_chars": 120,
"link_url_max_bytes": 2000,
"link_url_schemes": ["http", "https"],
"list_pagination": false
}extra numbers are checked against the domain constants by TestTasksAreaLimitsMatchDomain; string lengths come from the proto validation rules of api/catalog/catalog.proto.
7. How this was verified
go test -tags=integration ./internal/api/catalog/ -run 'TestTask'on PostgreSQL (cart_dev): CRUD, dense renumbering on moves inside and between columns, concurrent moves, filters, shop isolation, the leads/tasks board boundary, column and board deletion, PAT (tasks:readreads, no scope —PAT_SCOPE_MISSING, dev key writing —PAT_CONTOUR_MISMATCH).-run 'TestSandboxScopesByView': board and column endpoints by the board's kind —tasks:writemanages a task board and is refused on a leads board,forms:writethe other way round, a foreign board staysSANDBOX_NOT_FOUND.- Wire probe, 2026-09-24: the generated HTTP handlers of the Catalog service with the production JSON marshaler and gateway error writer, behind a stub service. Measured: int64 fields as strings,
extraas base64,extraas an object → 400,PATCHwithcolumn_id→ 400,DELETEwithout a body → 400 and with{}→ 200,assignee_user_id: 0reaches the handler as "present",GET /tasks?page.limit=5→ 200. - Domain unit tests of the
extrawhitelist (internal/core/domain/task_test.go) and this document's §6 against the constants (internal/api/discovery/area_scopes_test.go).
Исходник: https://api.vizen.shop/docs/tasks