Sincronização incremental com updated_since
Para manter uma cópia dos dados do Sursa no seu próprio sistema, você não precisa ler tudo a cada vez. As listas de
objetos que têm updated_at aceitam updated_since e retornam só os objetos alterados naquele momento ou depois
dele.
curl -G https://api.sursa.com/v1/cards \
-H "Authorization: Bearer $SURSA_API_KEY" \
--data-urlencode "updated_since=2026-10-23T00:00:00Z" \
--data-urlencode "limit=200"Com updated_since, a lista é ordenada por updated_at em ordem crescente e depois por id, e o
cursor continua nessa ordem. Assim, você lê as alterações na ordem em que aconteceram,
e o limite entre páginas nunca as separa de um jeito que faça uma delas se perder.
updated_since é aceito por estas listas:
| Lista | O que conta como alteração |
|---|---|
GET /v1/cards, GET /v1/cards/count | updated_at do cartão. |
GET /v1/groups | updated_at do grupo: o título ou as configurações dele. |
GET /v1/webhooks | updated_at do webhook: as configurações e o segredo dele, não as entregas. |
GET /v1/jobs | updated_at do job: cada mudança de status dele. |
GET /v1/drafts | updated_at do rascunho. |
GET /v1/cards/{card_id}/threads | A criação ou a edição mais recente do comentário raiz ou de qualquer resposta. A thread vem inteira. |
GET /v1/folders, GET /v1/collections/{name}/folders | updated_at da pasta. As pastas que atendem ao filtro vêm como uma lista plana; veja abaixo. |
As coleções, os usuários e os comunicados não têm updated_at, então as listas deles não filtram por ele: como são
curtas, leia-as inteiras a cada execução. O horário precisa ter um deslocamento de fuso horário, como Z ou +02:00;
qualquer outro valor é recusado, como em GET /v1/cards.
Com updated_since, uma árvore de pastas deixa de ser uma árvore: folders de cada coleção traz as pastas alteradas
como uma lista plana, com children vazio, e parent_id indica onde cada uma fica.
GET /v1/folders então omite as coleções sem pastas alteradas.
Um loop de sincronização
- Na primeira execução, leia a lista inteira sem
updated_sincee guarde o momento em que a execução começou. - A cada execução seguinte, passe esse momento guardado como
updated_since, menos uma pequena margem, como um minuto, e guarde o novo horário de início. - Grave cada objeto no seu armazenamento pelo
id, substituindo a cópia anterior. Os objetos que aparecem duas vezes por causa da margem simplesmente são gravados de novo. - Peça cada página seguinte com o cursor e o mesmo
updated_since. Um cursor só funciona com os filtros da primeira página: sem eles, a requisição retorna400com o códigoapi.cursor_invalid.
Usar o horário de início da execução, e não o horário em que ela terminou, garante que uma alteração feita durante a execução seja captada pela próxima.
GET /v1/cards retorna um array JSON, então a próxima página dele vem no cabeçalho X-Next-Cursor (veja
Listas que retornam um array):
const BASE = "https://api.sursa.com";
const headers = { Authorization: `Bearer ${process.env.SURSA_API_KEY}` };
async function syncCards(since, saveCard) {
const startedAt = new Date();
const params = new URLSearchParams({ limit: "200" });
if (since) params.set("updated_since", new Date(since.getTime() - 60_000).toISOString());
let query = params.toString();
while (query) {
const response = await fetch(`${BASE}/v1/cards?${query}`, { headers });
if (!response.ok) throw new Error(`HTTP ${response.status}: ${await response.text()}`);
for (const card of await response.json()) await saveCard(card); // write into your store by card.id
const cursor = response.headers.get("X-Next-Cursor");
if (cursor) params.set("cursor", cursor); // keep updated_since: the cursor needs the same filters
query = cursor ? params.toString() : "";
}
return startedAt; // pass it as `since` next time
}Exclusões
Uma lista só retorna objetos que existem, então ela não consegue avisar que algo foi excluído. Obtenha as exclusões no feed de eventos:
curl -G https://api.sursa.com/v1/webhook-events \
-H "Authorization: Bearer $SURSA_API_KEY" \
--data-urlencode "type=card.deleted" \
--data-urlencode "created_since=2026-10-23T00:00:00Z"Para ler o feed, é preciso uma chave com a função admin e o escopo webhooks:manage. Os eventos são mantidos por 30
dias. Se a sua sincronização ficou sem rodar por mais tempo que isso, leia a lista inteira de novo e remova do seu
armazenamento todo objeto que não estiver mais nela.
Exportar para um data warehouse junta as listas, updated_since e o feed de
eventos em um único job.