Integração
Exemplos práticos
Fluxos inteiros, do token à conclusão de um epic.
Montar um quadro do zero
Espaço, projeto, etiqueta, epic e o primeiro card, na ordem em que as dependências exigem.
export API=http://localhost:3000/api
export TOKEN="o token de Minha conta → Segurança"
auth=(-H "Authorization: Bearer $TOKEN" -H "Content-Type: application/json")
# 1. o espaço, que já nasce com as 5 colunas padrão
TENANT=$(curl -s "${auth[@]}" -X POST $API/tenants \
-d '{ "name": "Suporte", "color": "sky" }' | jq -r .id)
# 2. a coluna de entrada, do conjunto do espaço
COLUNA=$(curl -s "${auth[@]}" "$API/columns?tenant=$TENANT" | jq -r '.[0].id')
# 3. projeto e etiqueta
PROJETO=$(curl -s "${auth[@]}" -X POST $API/projects \
-d "{ \"tenantId\": \"$TENANT\", \"name\": \"Integrações\" }" | jq -r .id)
TAG=$(curl -s "${auth[@]}" -X POST $API/tags \
-d "{ \"tenantId\": \"$TENANT\", \"name\": \"api\" }" | jq -r .id)
# 4. o epic, que herda o espaço do projeto
EPIC=$(curl -s "${auth[@]}" -X POST $API/epics \
-d "{ \"projectId\": \"$PROJETO\", \"name\": \"Onboarding\" }" | jq -r .id)
# 5. o card
curl -s "${auth[@]}" -X POST $API/cards \
-d "{
\"tenantId\": \"$TENANT\",
\"title\": \"Publicar a documentação\",
\"columnId\": \"$COLUNA\",
\"projectId\": \"$PROJETO\",
\"epicId\": \"$EPIC\",
\"tagIds\": [\"$TAG\"]
}"const API = "http://localhost:3000/api";
const token = process.env.NOTEBUGS_TOKEN!;
async function api<T>(path: string, init: RequestInit = {}): Promise<T> {
const response = await fetch(API + path, {
...init,
headers: {
"Content-Type": "application/json",
Authorization: `Bearer ${token}`,
...init.headers,
},
});
// O codigo HTTP e o contrato; `error` e a frase para quem le.
if (!response.ok) {
const { error } = await response.json();
throw new Error(`${response.status}: ${error}`);
}
return response.json();
}
const tenant = await api<{ id: string }>("/tenants", {
method: "POST",
body: JSON.stringify({ name: "Suporte", color: "sky" }),
});
const colunas = await api<{ id: string }[]>(`/columns?tenant=${tenant.id}`);
const projeto = await api<{ id: string }>("/projects", {
method: "POST",
body: JSON.stringify({ tenantId: tenant.id, name: "Integrações" }),
});
const card = await api("/cards", {
method: "POST",
body: JSON.stringify({
tenantId: tenant.id,
title: "Publicar a documentação",
columnId: colunas[0].id,
projectId: projeto.id,
}),
});Mover um card e ler o histórico
# afterCardId null solta no topo da coluna de destino
curl -s "${auth[@]}" -X POST $API/cards/$CARD/move \
-d "{ \"columnId\": \"$OUTRA_COLUNA\", \"afterCardId\": null }"
# a troca de coluna virou uma linha de histórico
curl -s "${auth[@]}" $API/cards/$CARD/historyEditar em lote, sem perder etiqueta
# no lote as etiquetas são ADICIONA/REMOVE, e não a lista final,
# para não apagar as etiquetas que cada card já tinha
curl -s "${auth[@]}" -X PATCH $API/cards/bulk \
-d "{
\"cardIds\": [\"$C1\", \"$C2\", \"$C3\"],
\"branch\": \"release/2026-09\",
\"addTagIds\": [\"$TAG\"]
}"Marcar um prazo que vale o mesmo dia para todos
// Os dois campos andam juntos: o instante e o fuso em que ele foi
// DECLARADO. Sem `dueAtZone`, um prazo das 23h em Sao Paulo apareceria
// no dia seguinte para quem lesse em UTC.
await api(`/cards/${cardId}`, {
method: "PATCH",
body: JSON.stringify({
dueAt: "2026-10-15T23:59:00.000Z",
dueAtZone: "America/Sao_Paulo",
}),
});
// Num PATCH, `dueAtZone` ausente PRESERVA o fuso ja gravado.
// Para limpar o prazo, mande null explicito:
await api(`/cards/${cardId}`, {
method: "PATCH",
body: JSON.stringify({ dueAt: null }),
});Concluir um epic com cards pendentes
# sem decidir o destino dos pendentes, isto é 422
curl -s "${auth[@]}" -X PATCH $API/epics/$EPIC \
-d '{ "status": "DONE" }'
# conclui os pendentes onde eles estão…
curl -s "${auth[@]}" -X PATCH $API/epics/$EPIC \
-d '{ "status": "DONE", "pendingCards": "DONE" }'
# …ou manda o que sobrou para o próximo epic
curl -s "${auth[@]}" -X PATCH $API/epics/$EPIC \
-d "{
\"status\": \"DONE\",
\"pendingCards\": \"MOVE\",
\"pendingEpicId\": \"$PROXIMO\"
}"Anexar um arquivo
# multipart: sem Content-Type na mão, o curl escreve o boundary
curl -s -H "Authorization: Bearer $TOKEN" \
-F "[email protected]" \
$API/cards/$CARD/attachments
# a resposta traz a url pronta para o Markdown
# [print](/api/files/18ab694d-….png)const form = new FormData();
form.append("files", arquivo);
const response = await fetch(`${API}/cards/${cardId}/attachments`, {
method: "POST",
// Sem Content-Type: o proprio FormData escreve o boundary.
headers: { Authorization: `Bearer ${token}` },
body: form,
});
const [anexo] = await response.json();
console.log(anexo.url); // /api/files/<nome gerado pelo servidor>.pngGerar um backup e esperar ficar pronto
curl -s "${auth[@]}" -X POST $API/backup/job \
-d '{ "destination": "LOCAL" }'
# a exportação é um JOB: acompanhe até sair de PENDING/RUNNING
until [ "$(curl -s "${auth[@]}" $API/backup/job | jq -r .status)" = "READY" ]; do
sleep 2
done
curl -s "${auth[@]}" -o notebugs-backup.zip $API/backup/file