Pular para o conteúdo principal

Sincronização incremental

Como manter o seu sistema em dia com as vendas e os recebimentos de cada cliente, sem perder registros, guardando só uma data por cliente.

O que guardar por cliente​

DadoQuando muda
CNPJNunca
company_tokenNa aprovação e em cada reemissão
updated_since de vendasA cada rodada de /v1/sales
updated_since de recebimentosA cada rodada de /v1/payments

A rodada​

Uma rodada é buscar page=1, 2, 3... com o mesmo updated_since e os mesmos filtros, até has_more: false.

  1. Primeira vez: não envie updated_since. Vêm todos os registros dos últimos 90 dias, o máximo que a API entrega.
  2. Chame page=1. A resposta traz o next_updated_since: anote, ele é o ponto de partida da próxima rodada.
  3. Para cada item, faça upsert pelo id: atualize se já existe, insira se não.
  4. Se has_more for true, chame a página seguinte, com o mesmo updated_since.
  5. Quando has_more for false, a rodada acabou. Grave o next_updated_since da página 1 como o novo updated_since desse cliente e desse feed.
  6. Espere a próxima rodada (por exemplo, a cada 15 minutos ou de hora em hora) e volte ao passo 2.

:::warning Use o next_updated_since da página 1 Só a página 1 traz o next_updated_since; nas outras ele vem null. Ele marca o início da rodada, com 30 segundos de folga para trás: o que for alterado enquanto você pagina volta na rodada seguinte, e os registros desses 30 segundos vêm de novo (o upsert absorve). Não use a hora em que a rodada terminou. :::

Exemplo em JavaScript​

Node.js 18 ou mais novo (usa o fetch nativo). As funções getAccessToken, upsert, saveUpdatedSince e handleUnauthorized são do seu sistema.

const BASE_URL = process.env.BASE_URL
const sleep = (ms) => new Promise((resolve) => setTimeout(resolve, ms))

/**
* Uma rodada de um feed para um cliente.
* @param {object} client { companyToken, updatedSince: { sales, payments } }
* @param {'sales' | 'payments'} feed
*/
async function sync(client, feed) {
// Na primeira vez fica vazio: vêm os últimos 90 dias
const since = client.updatedSince[feed]
let nextSince = null
let page = 1

while (true) {
const url = new URL(`/v1/${feed}`, BASE_URL)
url.search = new URLSearchParams({
...(since && { updated_since: since }),
page: String(page),
page_size: '1000',
})

const response = await fetch(url, {
headers: {
Authorization: `Bearer ${await getAccessToken()}`, // reutilizado por 10 min
'X-Company-Token': client.companyToken,
},
})

if (response.status === 429) {
const seconds = Number(response.headers.get('Retry-After') ?? 1)
await sleep(seconds * 1000)
continue // repete a mesma página
}
if (response.status === 401) {
await handleUnauthorized(client) // ver "Quando dá 401"
return
}
if (!response.ok) {
throw new Error(`${feed} página ${page}: HTTP ${response.status}`)
}

const { data } = await response.json()
if (page === 1) nextSince = data.next_updated_since

for (const item of data.items) {
await upsert(feed, item.id, item)
}

if (!data.has_more) break
page += 1
}

// Só grava no fim: se a rodada cair no meio, a próxima recomeça do mesmo ponto
await saveUpdatedSince(client, feed, nextSince)
}

// Uso: um cliente por vez, vendas e depois recebimentos
for (const client of clients) {
await sync(client, 'sales')
await sync(client, 'payments')
}

Por que deduplicar pelo id​

Um registro aparece de novo sempre que é alterado. Por exemplo: uma venda cancelada ou um recebimento que passa de PENDING para PAID. Ele também pode vir repetido dentro da mesma rodada, se for alterado enquanto você pagina. Por isso:

  • Não trate cada item como um registro novo.
  • Sempre use o id como chave e substitua os dados antigos pelos novos.
  • Repetir uma rodada inteira (depois de uma falha, por exemplo) não causa problema: o upsert torna as repetições inofensivas.

Com upsert pelo id, nenhum registro se perde, mesmo que mude durante a paginação.

Se algo der errado​

SituaçãoO que fazer
A rodada caiu no meioRecomece do page=1 com o mesmo updated_since de antes.
Perdeu o updated_since guardadoUse uma data alguns dias para trás, ou não envie updated_since para recarregar os últimos 90 dias. O upsert absorve o que vier repetido.
Quer reprocessar um períodoFiltre por date_from/date_to, dentro dos últimos 90 dias.

Muitos clientes​

O limite por minuto soma todos os clientes da sua software house. Com muitos clientes:

  • Sincronize os clientes em fila, um ou poucos de cada vez, não todos ao mesmo tempo.
  • Use um único token de acesso para todos os clientes enquanto ele valer.
  • Ao receber 429, espere o Retry-After antes de repetir a mesma página.
  • Use page_size=1000 para fazer menos chamadas.

Primeira carga de um cliente grande​

A API entrega no máximo os últimos 90 dias, então a primeira carga é limitada a esse período. Se o volume for grande, páginas muito fundas demoram mais: divida a carga com date_from e date_to (por exemplo, um mês por vez), como em Consultar um dia ou período. Depois disso, siga com rodadas normais por updated_since.

Quando o cliente revoga​

Se um feed começar a dar 401 só para um cliente, siga o roteiro de Quando dá 401. Se o pedido estiver REVOKED, tire esse cliente da fila de sincronização até ele autorizar de novo.