Vizen Shop

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 pathCost
to track the team's worktasks/task-boards, /tasks with tasks:*no personal data, not tied to buyers
to process buyer requests from a formleads/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 boardthe 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 personassignee_user_id if you already know a member id (seen on an existing card or given by the owner); otherwise assignee_name as texta 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 pathScope on a task boardWhat it does
GET /sandboxes?view=taskstasks:readthe same list as GET /task-boards; without view it is the leads list (forms:read)
POST /sandboxes {name, view: "tasks"}tasks:writethe same as POST /task-boards
PUT /sandboxes/{id} {name} · DELETE /sandboxes/{id} {}tasks:writerename · delete an empty board
POST /sandboxes/reorder {ids}tasks:writeboard order; a list mixing both kinds needs both rights
POST /sandboxes/{sandbox_id}/columns {label}tasks:writea normal column at the end
PUT /sandbox-columns/{id} {label} · DELETE /sandbox-columns/{id} {}tasks:writerename · delete (its cards go to the intake; the intake is protected)
POST /sandboxes/{sandbox_id}/columns/reorder {ids}tasks:writecolumn order, the full list

Taskid, 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 pathScopeWhat it does
GET /task-boardstasks:readtask boards with their columns
POST /task-boards {name}tasks:writenew board with the three starter columns
GET /tasks?sandbox_id&column_id&assignee_user_id&overduetasks:readtasks by filter, ordered column_id, position, id; 0 / false — no filter
GET /tasks/{id}tasks:readone task
POST /taskstasks:writenew task at the end of a column; column_id: 0 — the board's intake
PATCH /tasks/{id}tasks:writepartial edit of fields; never the column or the order
PUT /tasks/{id}/position {column_id, position}tasks:writemove inside the column or to another column of the same board
DELETE /tasks/{id} with body {}tasks:writehard 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 both 19 and "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 bytes field. Send "extra": "eyJwcmlvcml0eSI6ImhpZ2gifQ==" for {"priority":"high"}. A JSON object in its place is refused with 400 INVALID_REQUEST. An empty card reads "extra": "e30=" ({}).
  • `column_id: 0` means the intake of the board on create. On move column_id is 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:00 reads back as 2026-10-01T15:00:00Z). Anything else is TASK_BAD_DUE_AT.
  • `PATCH` knows presence. A field you do not send is left alone. assignee_user_id: 0 removes 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: PATCH with column_id answers 400 invalid request body: column_id — moving is PUT /tasks/{id}/position.
  • `DELETE` needs a body: -d '{}'. Without it the gateway answers 400 invalid request body.
  • extra is 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

CodeHTTPWhen
TASK_NOT_FOUND404the task does not exist or belongs to another shop
SANDBOX_NOT_FOUND404POST /tasks with an unknown or foreign sandbox_id
TASK_BOARD_MISMATCH400the board is a leads board, or the column belongs to another board
TASK_BAD_EXTRA400extra outside the whitelist or its limits; the reason follows the code
TASK_BAD_ASSIGNEE400assignee_user_id is not a member of the shop
TASK_BAD_DUE_AT400due_at is not RFC3339
SANDBOX_NOT_EMPTY400DELETE /sandboxes/{id} of a task board that still has tasks
INVALID_REQUEST400the body does not parse: unknown field, extra not base64, no body on DELETE
PAT_SCOPE_MISSING403the 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_MISMATCH403a 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 exactly sandbox_id, column_id, assignee_user_id, overdue. Compare the count before and after a filter.
  • No pagination. GET /tasks returns 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 a mode through 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:read reads, no scope — PAT_SCOPE_MISSING, dev key writing — PAT_CONTOUR_MISMATCH).
  • -run 'TestSandboxScopesByView': board and column endpoints by the board's kind — tasks:write manages a task board and is refused on a leads board, forms:write the other way round, a foreign board stays SANDBOX_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, extra as base64, extra as an object → 400, PATCH with column_id → 400, DELETE without a body → 400 and with {} → 200, assignee_user_id: 0 reaches the handler as "present", GET /tasks?page.limit=5 → 200.
  • Domain unit tests of the extra whitelist (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

Работает в демо-режиме