Mecânica de sincronização incremental
Os endpoints inbound retornam registros ordenados pela data de criação (createdAt), usando keyset pagination: a cada resposta a API devolve um nextToken que…
Comum a todos os modelos (NFS-e, CT-e, NF-e). Este documento cobre o que é idêntico entre os endpoints
/api/integration/dfe/{modelo}/inbound/{documentos|eventos}de qualquer modelo: paginação por cursor, regras de token, semântica de polling, rate limit e o padrão de erros. Cada modelo tem sua própria página com endpoint, filtros específicos, exemplos de payload e os valores numéricos que lhe correspondem — ver seção "Por modelo" em Inbound.
1. Pré-requisitos
- Autenticação via JWT (Bearer token) válida. O
customerIdé extraído automaticamente do token. - Todos os timestamps devem ser informados em epoch milliseconds UTC.
2. Paginação por cursor (keyset pagination)
Os endpoints inbound retornam registros ordenados pela data de criação (createdAt), usando keyset pagination: a cada resposta a API devolve um nextToken que codifica a posição do último registro retornado (internamente, (createdAt, id)). Esse token é reenviado nas chamadas seguintes para avançar a partir daquele ponto — não é um OFFSET tradicional.
Registros atrasados são silenciosamente ignorados. Se um registro for persistido com
createdAtanterior à posição já ultrapassada pelo cursor (ex.: processamento fora de ordem no backend), ele não aparece em nenhuma página futura da sequência — o cursor já avançou além daquele ponto. Isso é uma propriedade inerente ao design de keyset porcreatedAt, não um bug. Na prática, isso é raro (a janela entre a criação e a indexação é curta) mas o client não deve assumir garantia de entrega de 100% dos registros por este mecanismo isoladamente; para reconciliação crítica, considere reprocessar periodicamente uma janelacreatedFromrecuada.
Semântica do hasMore
hasMore | Significado |
|---|---|
true | A página retornou a quantidade exata de registros solicitada (size). Provavelmente há mais dados. |
false | Menos registros que size foram retornados, ou nenhum. Não há mais dados neste momento — não significa fim do processo (ver § 6). |
3. Regras do token
Opacidade
O nextToken é uma string codificada que contém internamente a posição do cursor e um hash dos filtros utilizados para gerá-lo. O client não deve interpretar ou manipular o conteúdo do token — apenas armazená-lo e reenviá-lo.
A chave nextToken é omitida quando não há valor — nunca aparece como null
A API não serializa campos com valor null. Quando não há próxima página (resposta vazia, ou fim de uma sequência), o objeto data simplesmente não tem a chave nextToken — não é "nextToken": null. Um client que leia o JSON com acesso direto (data["nextToken"] em Python, data.nextToken tipado como não-opcional, etc.) quebra nesse caso; use uma leitura tolerante a chave ausente (data.get("nextToken"), Optional, valor default) — ver §9.
Imutabilidade dos filtros
Todos os parâmetros de filtro usados para gerar um nextToken devem permanecer idênticos em todas as chamadas daquela sequência de paginação (o conjunto exato de filtros varia por modelo/endpoint — ver a página específica). Se algum filtro for alterado com um token ativo, a API rejeita a requisição com HTTP 400 e um código {MODELO}_LISTING_TOKEN_FILTER_MISMATCH (ex.: CTE_LISTING_TOKEN_FILTER_MISMATCH, NFSE_LISTING_TOKEN_FILTER_MISMATCH). Para mudar os filtros, descarte o token e inicie uma nova sequência.
O token de um endpoint (documentos) não é intercambiável com o de outro (eventos), nem entre modelos.
4. Como paginar passo a passo
Passo 1 — Primeira chamada
Defina a janela temporal (createdFrom obrigatório) e os filtros desejados. Não envie nextToken.
Passo 2 — Páginas seguintes
Repita a chamada adicionando o nextToken retornado, com os mesmos filtros. Continue enquanto hasMore == true, sobrescrevendo o token a cada resposta.
Passo 3 — Fim dos dados
Em algum momento a resposta virá com menos registros que size, ou vazia — items: [], hasMore: false e sem a chave nextToken (ver nota na §3 sobre omissão de campos null). Não descarte o último token não-nulo que você já tinha — ele será usado para o polling contínuo.
Passo 4 — Polling contínuo
Reenvie a última chamada usando o último nextToken não-nulo armazenado, com os mesmos filtros. Novos registros criados desde a última consulta serão retornados. Se não houver novidades, a resposta volta vazia — aguarde um intervalo e tente de novo.
5. O que o client deve armazenar
| Dado | Por quê |
|---|---|
createdFrom | Ponto de partida fixo da janela. Não muda ao longo da sequência. |
createdTo (se usado) | Limite superior fixo, reenviado igual em todas as chamadas. |
| Filtros opcionais do endpoint | Reenviados identicamente enquanto o token estiver ativo. |
Último nextToken não-nulo | Cursor que permite retomar da posição exata, sem reprocessar desde createdFrom. |
Dica: armazene filtros + token como uma unidade (registro de banco ou JSON em disco), para restaurar o estado exato após falhas/reinícios sem risco de inconsistência.
6. Quando não houver mais registros
items: [] e hasMore: false significam que neste instante não há mais dados além do cursor — não que o processo terminou. Mecânica de polling incremental:
- Manter o último
nextTokennão-nulo. - Aguardar um intervalo (respeitando o rate limit).
- Repetir a chamada com o token e os mesmos filtros.
- Processar os novos registros, se houver.
- Repetir.
7. Rate limit
Isolado por customerId (tenant): cada tenant tem cota própria, sem interferência entre clientes distintos. O valor (requisições por janela) é específico de cada modelo/endpoint — ver a página correspondente — mas o mecanismo é sempre o mesmo:
- Excedeu a cota → HTTP
429, código{MODELO}_LISTING_RATE_LIMITED. - Recomenda-se distribuir o polling ao longo da janela (ex.: com cota de 5/60s, um intervalo de 12–15s entre chamadas evita rajadas).
- Em caso de 429, aplique backoff (ex.: aguardar o período de refresh completo antes de tentar novamente).
8. Padrão de erros
Todos os endpoints inbound seguem o mesmo conjunto de erros, com o código prefixado pelo modelo (NFSE_LISTING_*, CTE_LISTING_*, etc.):
| Erro (sufixo) | HTTP | Causa | Ação |
|---|---|---|---|
_INVALID_DATE_RANGE | 400 | createdTo <= createdFrom | Corrigir as datas. |
_INVALID_TOKEN | 400 | Token malformado/corrompido/versão não suportada | Descartar e reiniciar do createdFrom. |
_TOKEN_FILTER_MISMATCH | 400 | Filtros diferem dos que geraram o token | Reenviar filtros idênticos ou iniciar nova sequência. |
_RATE_LIMITED | 429 | Cota excedida | Aplicar backoff e tentar novamente. |
9. Exemplo genérico de loop de sincronização
FILTERS = { "createdFrom": 1710400000000, "size": 50 } # + filtros específicos do modelo/endpoint
POLL_INTERVAL_SECONDS = 15
next_token = storage.load("<modelo>_<endpoint>_token") # None na primeira execução
while True:
params = {**FILTERS}
if next_token is not None:
params["nextToken"] = next_token
response = http_get("/api/integration/dfe/{modelo}/inbound/{documentos|eventos}", params)
if response.status == 429:
wait(60); continue
data = response.json()["data"]
for item in data["items"]:
process(item)
# nextToken pode estar AUSENTE do JSON (não é uma chave "null") — use .get(), nunca data["nextToken"]
new_token = data.get("nextToken")
if new_token is not None:
next_token = new_token
storage.save("<modelo>_<endpoint>_token", next_token)
if data["hasMore"]:
continue # próxima página (respeitando rate limit)
wait(POLL_INTERVAL_SECONDS) # sem mais dados — polling
Pontos-chave
- Inicialização: na primeira execução
next_tokenlocal éNone; a chavenextTokennão é enviada na primeira chamada, e a API retorna os registros mais antigos a partir decreatedFrom, em ordem cronológica. - Leitura tolerante: sempre leia
nextTokencom.get()(ou equivalente) — a API omite a chave quando não há token, nunca a envia comonull. - Drenagem: enquanto
hasMore == true, busque páginas consecutivas (respeitando o rate limit). - Polling: quando
hasMore == false, espere e repita. - Idempotência: em falha/restart, recarregue o
nextTokendo storage e retome exatamente de onde parou — sem duplicatas.