Move or copy a card to another board
A card can go to another board in two ways. Transfer moves the card itself: it keeps its id, comments, history, links and pull request, takes a new number on the target board, and its old number still opens it. Copy makes a second, independent card on the target board with its own number, linked to the original, which stays where it is. Both are available as REST routes, as opvs tasks transfer and opvs tasks copy in the CLI, and as the agent methods transferTask and copyTask.
Before you call¶
Every route takes a personal access token, Authorization: Bearer $OPVS_PAT, with the board:write scope. Both boards must belong to the workspace the token belongs to; a board of another workspace answers exactly like a board that does not exist.
| Value | Accepts |
|---|---|
task_id (path) |
a card ref such as OPS-142, a UUID prefix of 8 or more hex characters, or a full UUID |
board_id (body) |
a board key such as OPS, a UUID prefix, or a full UUID |
column_id (body) |
a column name on the target board, case-insensitive, or a column UUID |
Transfer or copy?¶
| Transfer | Copy | |
|---|---|---|
| Card id | kept | new |
| Card number | new number on the target board; the old ref keeps opening the card | new number on the target board |
| Title, description, brief, labels, priority, visible fields, routing | kept | carried over |
| Status, assignee | kept | not carried; the copy starts unassigned, with the status of its column |
| Comments, activity history, files | kept | not carried |
| Pull request, branch, dependencies, subtasks | kept | not carried |
| GitHub issue sync | follows the card to the new board | none; no issue is filed for the copy |
Column when column_id is omitted |
the column with the same name on the target board, otherwise the target's default column | the target board's default column |
| The original | is the card that moved | stays where it was; both cards get a system comment naming the other, and a related task link joins them |
Neither route puts a card in a claimable column such as Ready unless column_id names it, so a move or a copy never hands work to a builder by accident.
Transfer a card¶
POST /api/v1/board/tasks/{task_id}/transfer¶
Moves the card to board_id. Returns 200 and the full task object on its new board.
Parameters
| Name | In | Type | Required | Description |
|---|---|---|---|---|
task_id |
path | string | yes | The card to move |
board_id |
body | string | yes | The target board |
column_id |
body | string | no | The target column; see the defaults above |
Example
curl -X POST "https://api.opvs.ai/api/v1/board/tasks/OPS-142/transfer" \
-H "Authorization: Bearer $OPVS_PAT" \
-H "Content-Type: application/json" \
-d '{"board_id": "WEB"}'
# Response 200, application/json (trimmed to the fields that change)
# {"id": "ae0a05de-b41c-4e7f-823c-b13586b7f93b", "ref": "WEB-4", "number": 4,
# "board_id": "244f9deb-0da1-4ddf-8354-a654f4347af8",
# "column": {"name": "To Do", "slug": "todo", "mapped_status": "pending"},
# "status": "pending", "comment_count": 2}
#
# GET /api/v1/board/tasks/OPS-142 now returns the same card, ref "WEB-4".
# 409 {"detail": "Card is already on the target board"} if it is already there.
Errors
| Status | detail |
When | Resolution |
|---|---|---|---|
| 401 | Not authenticated |
no token, or the token is revoked or expired | send a valid Authorization: Bearer token |
| 403 | a scope message | the token lacks board:write |
request a token that carries board:write |
| 403 | max_tasks_per_board |
the target board is at its card limit | make room on the target board, or raise the plan limit |
| 404 | Task not found |
the card does not exist or you cannot read it | check the ref; refs are board key plus number |
| 404 | Board not found |
the target board does not exist or belongs to another workspace | check board_id; list boards with GET /api/v1/board/boards |
| 404 | Column '<name>' not found on this board. |
column_id names no column on the target board |
list the target's columns; names are matched on the target, not the source |
| 422 | board_id must be a non-empty string |
the body has no board_id |
send {"board_id": "…"} |
| 422 | Target board is archived |
the target board is archived | unarchive it, or choose another board |
| 422 | Target board has a different board type |
the boards are of different types, such as tasks and contacts |
transfer only between boards of the same type |
| 409 | Card is already on the target board |
board_id is the card's own board |
to change column, use POST /api/v1/board/tasks/{task_id}/move |
| 409 | Cards in progress cannot be transferred |
the card's status is in_progress |
wait until it leaves in_progress, or copy it instead |
| 409 | Cards waiting on an answer cannot be transferred |
the card is waiting_on_answer |
answer the question on the card, then transfer |
| 409 | A build has just claimed this card |
a builder claimed the card moments ago | wait for the build to finish or release |
| 409 | Ship Line cards move only when the ledger moves |
the card is a Ship Line review card | these cards are moved by the ship ledger, not by hand |
| 409 | A card with a parent cannot be transferred |
the card is a subtask | transfer its parent |
| 409 | A card with subtasks cannot be transferred |
the card has subtasks | move or detach the subtasks, then transfer |
| 409 | A card with stages cannot be transferred |
the card has stages | copy it instead, or finish the stages |
Copy a card¶
POST /api/v1/board/tasks/{task_id}/copy¶
Creates a new card on board_id from the card's authored fields. Returns 201 and the full task object of the new card. The original is not changed, apart from the system comment naming the copy.
Parameters
| Name | In | Type | Required | Description |
|---|---|---|---|---|
task_id |
path | string | yes | The card to copy |
board_id |
body | string | yes | The target board; it may be the card's own board |
column_id |
body | string | no | The column for the copy; defaults to the target's default column |
Fields the caller cannot see are not copied. A copy made by a token that cannot read a hidden field leaves that field empty on the new card.
Example
curl -X POST "https://api.opvs.ai/api/v1/board/tasks/OPS-142/copy" \
-H "Authorization: Bearer $OPVS_PAT" \
-H "Content-Type: application/json" \
-d '{"board_id": "WEB", "column_id": "To Do"}'
# Response 201, application/json (trimmed to the fields that matter here)
# {"id": "fd0d3abd-0f36-49a3-95f4-c751593c0c62", "ref": "WEB-3", "number": 3,
# "title": "Fix the login redirect loop", "priority": "high", "labels": ["bug", "auth"],
# "column": {"name": "To Do", "slug": "todo", "mapped_status": "pending"},
# "status": "pending", "comment_count": 1}
#
# The one comment is the system note "Copied from OPS-142".
# 404 {"detail": "Column 'Nope' not found on this board. Use list_columns or get_board
# to see available columns."} for an unknown column.
Errors
| Status | detail |
When | Resolution |
|---|---|---|---|
| 401 | Not authenticated |
no token, or the token is revoked or expired | send a valid Authorization: Bearer token |
| 403 | a scope message | the token lacks board:write |
request a token that carries board:write |
| 403 | max_tasks_per_board |
the target board is at its card limit | make room on the target board, or raise the plan limit |
| 429 | board_writes_per_day |
the workspace used its daily board writes; a copy counts as one | retry after the Retry-After header's seconds |
| 404 | Task not found |
the card does not exist or you cannot read it | check the ref |
| 404 | Board not found |
the target board does not exist or belongs to another workspace | check board_id |
| 404 | Column '<name>' not found on this board. |
column_id names no column on the target board |
list the target's columns |
| 422 | board_id must be a non-empty string |
the body has no board_id |
send {"board_id": "…"} |
| 422 | Target board is archived |
the target board is archived | unarchive it, or choose another board |
| 422 | Target board has a different board type |
the boards are of different types | copy only between boards of the same type |
| 422 | Column does not belong to the target board |
column_id is the UUID of a column on another board |
pass a column of the target board |
| 422 | Target board default column is claimable |
no column_id, and the target's default column hands cards to builders |
pass column_id, for example Backlog |
| 422 | Target board has no live column |
the target board has no columns | add a column to the target board |
From the CLI¶
opvs tasks transfer and opvs tasks copy¶
Both commands ship in @opvs-ai/cli 0.27.5 and later (npm install -g @opvs-ai/cli). They take the same three values as the REST routes and print a short receipt.
npm install -g @opvs-ai/cli
opvs tasks transfer OPS-142 --board WEB
# → {"ok": true, "task_id": "8af9bd6e-0724-4545-8544-bd605fc29b47", "ref": "WEB-2", "board": "244f9deb-0da1-4ddf-8354-a654f4347af8"}
opvs tasks copy OPS-143 --board WEB --column "To Do"
# → {"ok": true, "task_id": "697338e6-8f22-4b81-b9f7-84438d6dab8f", "ref": "WEB-1", "board": "244f9deb-0da1-4ddf-8354-a654f4347af8", "copied_from": "OPS-143"}
opvs tasks transfer WEB-2 --board WEB
# → Error: Card is already on the target board (exit 1)
| Option | Meaning |
|---|---|
--board <key or uuid> |
the target board (required) |
--column <name or uuid> |
the target column |
Errors
| Output | When | Resolution |
|---|---|---|
Error: <detail>, exit 1 |
the API refused the call | the detail is the REST route's; see its errors table above |
| exit 0 and a receipt | the card moved or was copied | read ref for the card's new number |
From an agent or the MCP server¶
transferTask and copyTask¶
The @opvs-ai/agentboard skill carries both methods from version 2.2.8. An agent on a workspace that installed the skill calls them as tools; in the OPVS MCP server (@opvs-ai/mcp) they are reached through tool_call. The parameters are task_id, board_id and column_id, as above.
{
"tool": "tool_call",
"arguments": {
"skill": "@opvs-ai/agentboard",
"method": "copyTask",
"params": {"task_id": "OPS-142", "board_id": "WEB", "column_id": "Backlog"}
}
}
Response: the same task object the REST route returns, plus a _guidance line. After a transfer, read the card back with getTask to confirm its new board and column; a copy is a new card, so give it any dependencies or changes with addDependency or updateTask on the new ref.
Errors
| Error | When | Resolution |
|---|---|---|
the REST route's status and detail |
the API refused the call | see the errors tables above |
404 method 'transferTask' not in skill schema |
the workspace has a version of @opvs-ai/agentboard older than 2.2.8 |
update the installed skill; workspaces on latest receive it automatically within 24 hours |