Criar, ler, editar, mover e apagar, mais as duas rotas de lote.
O card é a entidade de primeiro nível: tenantId é obrigatório na criação e imutável dali em diante. Antes de mandar columnId, confira qual conjunto de colunas vale para o card.
POST/api/cards
Papel: MembroEspaço de trabalho
Cria um card, do zero ou a partir de um modelo.
title e columnId deixam de ser obrigatórios quando vem templateId: a composição é “o que veio no pedido vence, o modelo preenche o que faltou”. O que sobrar vazio depois disso é recusado com 422.
Corpo da requisição
Campo
Tipo
Descrição
tenantIdobrigatório
cuid
Obrigatório e IMUTÁVEL: o card nasce dentro de um espaço e nunca sai dele.
templateId
cuid | null
O modelo que preenche o que o corpo não trouxe. Precisa ser do mesmo espaço.
title
string
description
string
columnId
cuid
Precisa ser do conjunto em vigor para este card; ver columnScope.
projectId
cuid | null
null deixa o card explicitamente sem projeto.
epicId
cuid | null
Precisa ser um epic do MESMO projeto do card.
branch
string | null
Campo livre. String vazia é normalizada para null.
dueAt
ISO 8601 | null
Instante ISO 8601. Sem dueAtZone, o prazo não carrega o fuso em que foi declarado.
dueAtZone
IANA | null
O fuso em que o prazo foi DECLARADO (America/Sao_Paulo). É o que faz o mesmo dia valer para todo leitor.
tagIds
cuid[]
A lista FINAL de etiquetas, todas do mesmo espaço.
Requisição
curl-s-X POST http://localhost:3000/api/cards \-H"Authorization: Bearer $TOKEN"\-H"Content-Type: application/json"\-d'{
"tenantId": "cmsp4djx60002p801o7ybpkv7",
"title": "Publicar a documentação",
"description": "Site estático com a referência da API.",
"columnId": "cmttg1vdl000tlf01uuqhz8ad",
"projectId": "cmttg1vjy000zlf015g097nhi",
"branch": "feature/api-documentation",
"tagIds": ["cmttg1vnk0011lf01q0eswms5"]
}'
const response =awaitfetch("http://localhost:3000/api/cards",{method:"POST",headers:{"Content-Type":"application/json",Authorization:`Bearer ${token}`,},body: JSON.stringify({
tenantId,title:"Publicar a documentação",
columnId,// O modelo preenche o que o corpo não trouxer.
templateId,}),});if(!response.ok){const{ error }=await response.json();thrownewError(error);}const card =await response.json();
Resposta201
{"id":"cmttg1vv00017lf01ol5bnlx2","number":198,"title":"Publicar a documentação","description":"Site estático com a referência da API.","columnId":"cmttg1vdl000tlf01uuqhz8ad","projectId":"cmttg1vjy000zlf015g097nhi","epicId":"cmttg1vs50015lf010l6k38uq","branch":"feature/api-documentation","dueAt":"2026-11-01T23:58:59.999Z","dueAtZone":"America/Sao_Paulo","favorite":false,"position":0,"createdAt":"2026-09-09T01:52:58.764Z","updatedAt":"2026-09-09T01:52:58.764Z","tags":[{"id":"cmttg1vnk0011lf01q0eswms5","name":"api","color":"mint"}],"attachments":[],"commentCount":0}
Respostas de erro
Código
Quando acontece
401
Sem credencial, ou com uma que não vale mais. Mande o cookie de sessão ou o cabeçalho Authorization: Bearer.
403
A conta não alcança o espaço de trabalho do recurso. É 403 e não 404 de propósito: assim a resposta não revela quais espaços existem.
404
O recurso não existe, ou já foi apagado.
422
O epic informado não é do mesmo projeto do card. A regra vale nos dois sentidos: mudar o projeto do card também é conferido.
422
O corpo não passou pelo schema. O campo details traz fieldErrors e formErrors do zod, campo a campo.
O card nasce na primeira coluna do conjunto e no topo, com número sequencial que nunca é reaproveitado, e a criação já grava a gênese do histórico. Entrando num epic com alvo de data, o prazo pode ser herdado dele.
GET/api/cards/[id]
Papel: LeitorEspaço de trabalho
Um card, com etiquetas, anexos e a contagem de comentários.
{"id":"cmttg1vv00017lf01ol5bnlx2","number":198,"title":"Publicar a documentação","description":"Site estático com a referência da API.","columnId":"cmttg1vdl000tlf01uuqhz8ad","projectId":"cmttg1vjy000zlf015g097nhi","epicId":"cmttg1vs50015lf010l6k38uq","branch":"feature/api-documentation","dueAt":"2026-11-01T23:58:59.999Z","dueAtZone":"America/Sao_Paulo","favorite":false,"position":0,"createdAt":"2026-09-09T01:52:58.764Z","updatedAt":"2026-09-09T01:52:58.764Z","tags":[{"id":"cmttg1vnk0011lf01q0eswms5","name":"api","color":"mint"}],"attachments":[],"commentCount":0}
Respostas de erro
Código
Quando acontece
401
Sem credencial, ou com uma que não vale mais. Mande o cookie de sessão ou o cabeçalho Authorization: Bearer.
403
A conta não alcança o espaço de trabalho do recurso. É 403 e não 404 de propósito: assim a resposta não revela quais espaços existem.
404
O recurso não existe, ou já foi apagado.
favorite é a resposta para QUEM PEDIU: a marca é pessoal e mora numa linha própria, então dois leitores recebem valores diferentes para o mesmo card.
PATCH/api/cards/[id]
Papel: MembroEspaço de trabalho
Edita um card. Campo ausente não é tocado.
Aceita { dueAt } ou { favorite } sozinhos: é o que a estrela e o seletor de prazo da interface usam, sem rota própria para cada um.
Parâmetros de rota
Campo
Tipo
Descrição
idobrigatório
cuid
Corpo da requisição
Campo
Tipo
Descrição
title
string
description
string
columnId
cuid
Trocar de coluna aqui grava histórico, igual ao arraste.
projectId
cuid | null
epicId
cuid | null
null tira o card do epic; o card continua no projeto.
branch
string | null
dueAt
ISO 8601 | null
dueAtZone
IANA | null
Ausente PRESERVA o fuso já gravado: não cai no fuso de quem está chamando.
favorite
boolean
Marca pessoal. Nenhuma regra de epic, prazo ou histórico é acionada por este campo.
tagIds
cuid[]
A lista FINAL: o que não vier aqui é removido do card.
Requisição
# a estrela da interface manda só istocurl-s-X PATCH http://localhost:3000/api/cards/cmttg1vv00017lf01ol5bnlx2 \-H"Authorization: Bearer $TOKEN"\-H"Content-Type: application/json"\-d'{ "favorite": true }'
// Prazo e fuso viajam juntos: é o que faz o dia valer igual para todos.awaitfetch(`http://localhost:3000/api/cards/${cardId}`,{method:"PATCH",headers:{"Content-Type":"application/json",Authorization:`Bearer ${token}`,},body: JSON.stringify({dueAt:"2026-10-15T12:00:00.000Z",dueAtZone:"America/Sao_Paulo",}),});
Resposta200
{"id":"cmttg1vv00017lf01ol5bnlx2","number":198,"title":"Publicar a documentação","description":"Site estático com a referência da API.","columnId":"cmttg1vdl000tlf01uuqhz8ad","projectId":"cmttg1vjy000zlf015g097nhi","epicId":"cmttg1vs50015lf010l6k38uq","branch":"feature/api-documentation","dueAt":"2026-11-01T23:58:59.999Z","dueAtZone":"America/Sao_Paulo","favorite":true,"position":0,"createdAt":"2026-09-09T01:52:58.764Z","updatedAt":"2026-09-09T01:52:58.764Z","tags":[{"id":"cmttg1vnk0011lf01q0eswms5","name":"api","color":"mint"}],"attachments":[],"commentCount":0}
Respostas de erro
Código
Quando acontece
401
Sem credencial, ou com uma que não vale mais. Mande o cookie de sessão ou o cabeçalho Authorization: Bearer.
403
A conta não alcança o espaço de trabalho do recurso. É 403 e não 404 de propósito: assim a resposta não revela quais espaços existem.
404
O recurso não existe, ou já foi apagado.
422
O corpo veio vazio. Campo ausente significa não mexe, então um PATCH sem nenhum campo não teria efeito nenhum.
422
O epic informado não é do mesmo projeto do card. A regra vale nos dois sentidos: mudar o projeto do card também é conferido.
Limpar um campo exige valor explícito (null). Ausente significa “não mexe”, e é o que permite mandar um campo só.
DELETE/api/cards/[id]
Papel: MembroEspaço de trabalho
Apaga um card, com comentários, vínculos, histórico e os arquivos do volume.
Sem credencial, ou com uma que não vale mais. Mande o cookie de sessão ou o cabeçalho Authorization: Bearer.
403
A conta não alcança o espaço de trabalho do recurso. É 403 e não 404 de propósito: assim a resposta não revela quais espaços existem.
404
O recurso não existe, ou já foi apagado.
POST/api/cards/[id]/move
Papel: MembroEspaço de trabalho
Move o card para uma coluna e uma posição: é por aqui que passa o arraste.
A posição chega como afterCardId, e nunca como um índice: com um filtro ligado, o índice visível não corresponde ao índice real da coluna.
Parâmetros de rota
Campo
Tipo
Descrição
idobrigatório
cuid
Corpo da requisição
Campo
Tipo
Descrição
columnIdobrigatório
cuid
A coluna de destino, do conjunto em vigor para este card.
afterCardIdobrigatório
cuid | null
O card depois do qual este fica. null solta no topo da coluna.
Requisição
# afterCardId null solta o card no TOPO da colunacurl-s-X POST http://localhost:3000/api/cards/cmttg1vv00017lf01ol5bnlx2/move \-H"Authorization: Bearer $TOKEN"\-H"Content-Type: application/json"\-d'{
"columnId": "cmttg1vdl000ulf01nhqhd23w",
"afterCardId": null
}'
Resposta200
{"id":"cmttg1vv00017lf01ol5bnlx2","number":198,"title":"Publicar a documentação","description":"Site estático com a referência da API.","columnId":"cmttg1vdl000ulf01nhqhd23w","projectId":"cmttg1vjy000zlf015g097nhi","epicId":"cmttg1vs50015lf010l6k38uq","branch":"feature/api-documentation","dueAt":"2026-11-01T23:58:59.999Z","dueAtZone":"America/Sao_Paulo","favorite":false,"position":0,"createdAt":"2026-09-09T01:52:58.764Z","updatedAt":"2026-09-09T01:52:58.764Z","tags":[{"id":"cmttg1vnk0011lf01q0eswms5","name":"api","color":"mint"}],"attachments":[],"commentCount":0}
Respostas de erro
Código
Quando acontece
401
Sem credencial, ou com uma que não vale mais. Mande o cookie de sessão ou o cabeçalho Authorization: Bearer.
403
A conta não alcança o espaço de trabalho do recurso. É 403 e não 404 de propósito: assim a resposta não revela quais espaços existem.
404
O recurso não existe, ou já foi apagado.
422
A coluna não pertence ao conjunto em vigor para este card. Qual conjunto vale é decisão do columnScope.
Trocar de coluna grava uma linha de histórico; reordenar dentro da mesma coluna não gera registro.
PATCH/api/cards/bulk
Papel: MembroEspaço de trabalho
Edita os detalhes de vários cards de uma vez.
As etiquetas viajam como ADICIONA/REMOVE, e não como a lista final que o formulário de um card envia: substituir a lista apagaria as etiquetas que cada card já tinha.
Corpo da requisição
Campo
Tipo
Descrição
cardIdsobrigatório
cuid[]
De 1 a 200 cards. Todos os espaços envolvidos são conferidos.
branch
string | null
dueAt
ISO 8601 | null
null limpa o prazo dos cards do lote.
dueAtZone
IANA | null
addTagIds
cuid[]
Etiquetas a acrescentar. Um id não pode estar aqui e em removeTagIds.
Sem credencial, ou com uma que não vale mais. Mande o cookie de sessão ou o cabeçalho Authorization: Bearer.
403
A conta não alcança o espaço de trabalho do recurso. É 403 e não 404 de propósito: assim a resposta não revela quais espaços existem.
404
O recurso não existe, ou já foi apagado.
422
O lote não escolheu nenhum campo para alterar, ou pediu para adicionar e remover a mesma etiqueta.
O lote recusa inteiro: se um card quebra uma regra, nenhum é alterado. Título e coluna ficam de fora: são decisões card a card.
PATCH/api/cards/epic
Papel: MembroEspaço de trabalho
Move cards para dentro, para fora e entre epics.
As três são a mesma operação: gravar epicId (ou null) num conjunto de cards.
Corpo da requisição
Campo
Tipo
Descrição
cardIdsobrigatório
cuid[]
epicIdobrigatório
cuid | null
null tira os cards do epic; eles continuam no projeto.
alignDueAt
boolean
Opt-in EXPLÍCITO para sobrescrever a data de quem vence depois do alvo do epic. Sem ele, o lote recusa em vez de apagar uma data escolhida.Ausente: false
Sem credencial, ou com uma que não vale mais. Mande o cookie de sessão ou o cabeçalho Authorization: Bearer.
403
A conta não alcança o espaço de trabalho do recurso. É 403 e não 404 de propósito: assim a resposta não revela quais espaços existem.
404
O recurso não existe, ou já foi apagado.
422
O epic está travado e não recebe cards novos. Destrave antes, ou escolha outro.
422
Algum card do lote vence depois do alvo do epic. Mande alignDueAt: true para sobrescrever as datas: o servidor nunca apaga uma data escolhida sem pedido.