O quadro
Todo caso do Lens ocupa uma raia do quadro kanban do workspace, e o campo kanbanId do caso diz qual. O quadro tem três fases, na ordem do trabalho: COLETA, ANALISE e DESFECHO.
O Lens move o caso sozinho a cada acontecimento; o cliente move no que é decisão humana.
O quadro nasce pronto
Um workspace que nunca leu o quadro recebe as nove raias padrão na primeira chamada de GET .../kanban, gravadas ali mesmo, com o autor system:
| Fase | Ordem | Raia | Quando o caso está nela |
|---|---|---|---|
COLETA | 1 | Abertura | O caso acabou de nascer |
COLETA | 2 | Dados pendentes | Falta material (movimento do analista) |
COLETA | 3 | Dados inconsistentes | O envio dos documentos falhou |
COLETA | 4 | Pronto para análise | O envio terminou bem |
ANALISE | 1 | Analisando | A análise está na fila ou rodando |
ANALISE | 2 | Análise falhou | A análise terminou em FAILED |
ANALISE | 3 | Analisado | A análise terminou em DONE |
DESFECHO | 1 | Reprovado | O analista reprovou o caso |
DESFECHO | 2 | Aprovado | O analista aprovou o caso |
Os ids das nove são estáveis: derivam do workspace, da fase e do nome da raia, então são os mesmos em toda leitura, para sempre. Duas chamadas simultâneas no mesmo workspace gravam cada raia uma única vez.
curl "https://services.zarv.com/api/v1/lens/$WORKSPACE_ID/kanban" \
-H "Authorization: Bearer $ACCESS_TOKEN"[
{
"id": "0f3b5d21-9c47-5e8a-b6d2-1a4c7e9f03b8",
"workspaceId": "3f1a2b4c-5d6e-4f70-8a91-b2c3d4e5f607",
"userId": "system",
"phase": "COLETA",
"step": "Abertura",
"description": "",
"order": 1,
"status": "ACTIVE",
"createdAt": "2026-11-04T11:59:00Z",
"updatedAt": "2026-11-04T11:59:00Z"
}
]A lista vem na ordem do quadro: primeiro a fase, depois o order dentro da fase e, no empate, a criação. Não há paginação — o quadro tem poucas raias.
Raias INACTIVE continuam na lista: esconder uma raia é decisão do cliente, e um caso parado numa raia inativa não some do banco.
Quem move o caso
O Lens, nos acontecimentos
| Acontecimento | Raia de destino |
|---|---|
| O caso é aberto | Abertura |
| O envio dos documentos termina bem | Pronto para análise |
| O envio falha | Dados inconsistentes |
| A análise entra na fila | Analisando |
A análise termina em DONE | Analisado |
A análise termina em FAILED | Análise falhou |
Esses movimentos são automáticos e acontecem onde quer que o caso esteja: um caso que o analista havia levado para "Dados pendentes" e que recebe um envio novo volta a andar pela fase de Coleta.
O cliente, pelo PUT do caso
curl -X PUT "https://services.zarv.com/api/v1/lens/$WORKSPACE_ID/research/$RESEARCH_ID" \
-H "Authorization: Bearer $ACCESS_TOKEN" \
-H "Content-Type: application/json" \
-d '{ "kanbanId": "9d4a1c83-7e26-5f40-ab58-2c6d9e0f4a71" }'Regras do movimento:
- a raia tem de ser uma linha
ACTIVEdo quadro deste workspace; qualquer outra coisa é400; - com o envio dos documentos ou a análise em andamento, o movimento é
409— naquele momento, o caso é do worker; - o Lens não restringe o destino. Na prática, o movimento humano é para Dados pendentes, Aprovado ou Reprovado.
Montar as colunas
Para cada raia, peça a página dela na listagem de casos:
curl "https://services.zarv.com/api/v1/lens/$WORKSPACE_ID/research?kanbanId=$LANE_ID&limit=20" \
-H "Authorization: Bearer $ACCESS_TOKEN"O total da resposta passa a ser o total daquela raia — é o número que vai no cabeçalho da coluna —, e data é a página. Os filtros se combinam: ?kanbanId=…&status=CREATED&search=TST1A23 é a coluna filtrada.
Cada item da lista traz vehicleCount, entityCount e documentCount, que saem da mesma fonte do detalhe: o card e a tela do caso nunca mostram números diferentes.
async function loadBoard(workspaceId, accessToken) {
const headers = { Authorization: `Bearer ${accessToken}` };
const base = `https://services.zarv.com/api/v1/lens/${workspaceId}`;
const lanes = await fetch(`${base}/kanban`, { headers }).then((r) =>
r.json(),
);
return Promise.all(
lanes
.filter((lane) => lane.status === "ACTIVE")
.map(async (lane) => {
const page = await fetch(
`${base}/research?kanbanId=${lane.id}&limit=20`,
{ headers },
).then((r) => r.json());
return { lane, cases: page.data, total: page.total };
}),
);
}A Abertura leva os casos antigos
Um caso aberto antes de o Lens guardar a raia não tem kanbanId. Ele aparece na Abertura: tanto a leitura do caso quanto o filtro ?kanbanId=<Abertura> o trazem para lá. Nenhum caso fica fora do quadro.
O quadro é leitura
As rotas públicas do quadro são as duas leituras: a lista e a raia por id. Criar, editar ou apagar raia não é exposto — o quadro das nove raias é o mesmo em todos os workspaces, e é o que o Lens conhece para mover o caso sozinho.
A raia não decide nada
A raia reflete o que já aconteceu. Nenhuma regra do Lens a lê para decidir se um caso pode ser analisado, editado ou enviado: essa decisão é sempre de um campo próprio — documents.status, analysis.status, o status do caso.
Isso é de propósito. Se a raia decidisse, mover um card na tela mudaria o que o sistema permite — e um analista que arrasta um caso para "Aprovado" estaria, sem saber, liberando ou travando um fluxo. Aqui, ele está registrando uma decisão que já tomou.