Carga Incremental por Data de Atualização — Documentação Complementar

Como ler tabelas grandes em pacotes e manter o seu sistema atualizado lendo apenas o que mudou no ERP Spalla

1. Introdução


Esta página é uma documentação complementar da API do ERP Spalla. Ela se dirige ao integrador (ou ao agente de IA) que lê dados do ERP para outro sistema — um painel de BI, um data warehouse, um e-commerce, um CRM — e precisa mantê-lo atualizado sem sobrecarregar a API.

Muitas tabelas do ERP guardam, em cada registro, a data e a hora da última atualização. O servidor preenche esse valor sozinho a cada inclusão e a cada alteração. Com ele, a integração resolve dois problemas de uma vez:

ObjetivoComo a data de atualização ajuda
Dividir a carga de tabelas muito grandesEm vez de uma consulta aberta, ou de um período longo, que monta tudo numa resposta só, a leitura é feita em pacotes pequenos, cada um começando onde o anterior terminou.
Manter a sua base atualizadaDepois da primeira carga, cada ciclo lê só o que foi incluído ou alterado desde o ciclo anterior. Um registro antigo que foi alterado ganha data nova e volta sozinho para a fila de leitura.

Em uma frase: não releia a tabela inteira a cada sincronização. Leia uma vez, em pacotes, e depois leia só o que mudou, também em pacotes. Cada ciclo passa a trazer uma fração pequena da tabela, e o seu sistema fica poucos minutos atrás do ERP.

Esta página descreve a estratégia e a ordem das chamadas. Ela não substitui o dicionário oficial da sua instância, que é a fonte autoritativa dos nomes de tabela, de coluna e da chave primária.

Usa um agente de IA para escrever a integração? O Spalla API Toolkit ensina esta estratégia a ele: o agente propõe a carga incremental sozinho quando a tarefa pede volume e escreve o código com as três rotinas desta página. Veja a seção Spalla API Toolkit.

2. Onde encontrar o dicionário oficial da sua API


Cada empresa que opera o ERP Spalla expõe o seu próprio dicionário automático em um endereço próprio. Para acessá-lo, abra no navegador a URL no formato abaixo, substituindo os placeholders pelos valores da sua instância:

https://<host>:<porta>/root/dicionario?userToken=<token>

Para esta página, o dicionário responde três perguntas sobre cada tabela: qual é a coluna de data de atualização, quais colunas formam a chave primária e quais colunas o seu sistema realmente precisa.

3. Como a API funciona


Todas as leituras desta página passam pelo mesmo endpoint da API, com a arquitetura unificada: o verbo HTTP não determina a operação; o campo "method" do corpo JSON determina.

ItemValor
Endpoint/root/integrador
Verbo HTTPPATCH (para leitura, ferramentas que não enviam PATCH, como o Power BI, podem usar POST ou GET)
AutenticaçãoHeader userToken
Operação"method": "GET" no corpo, sobre "objectType": "TABLE"

Os parâmetros da consulta que fazem a carga incremental:

ParâmetroPapel na carga incremental
whereFiltro pela data de atualização, com um item "type": "expression": "ME_DTATU >= '2026-09-30 08:14:57'". A data vai com espaço entre a data e a hora, e a hora é obrigatória.
orderByA ordem dos registros. Na sincronização, pela data de atualização; na carga inicial e na reconciliação, pela chave primária.
limitTamanho do pacote: quantos registros cada resposta traz.
offsetNúmero da página, a partir de 1 — não é a quantidade de registros a pular. Com "limit": 500, "offset": 1 traz os registros 1 a 500 e "offset": 2 traz os registros 501 a 1000. Use sempre junto com limit e orderBy.
fieldsSó as colunas que o seu sistema usa. Em tabelas com centenas de colunas, é o que mais reduz o tamanho da resposta.

Três termos aparecem em toda a página:

Atenção — o formato da resposta muda com a quantidade de registros. Com dois ou mais registros, a resposta é uma lista. Com um registro, é o próprio objeto, sem a lista em volta. Com nenhum registro, a resposta é o HTTP 404 com a mensagem Nao foram encontrados resultados para a requisicao do tipo GET com os parametros informados [ rowCount:0 ]. Normalize antes de contar ou percorrer: objeto vira lista de um item, e o 404 com rowCount:0 vira lista vazia — um pacote incompleto, e não um erro.

Atenção — consulta sem limit traz tudo de uma vez. A API não impõe um limite de registros por conta própria: sem limit, a resposta contém o resultado inteiro do filtro. Numa tabela de movimento, é esse o pedido que deixa a integração lenta e disputa recursos com os usuários do ERP.

A montagem da requisição — URL base, porta e o header userToken — e as regras de formatação de valores estão na documentação geral da API, em especial Datas, Horas e Data/Hora. Esta página mostra apenas o corpo JSON de cada consulta; os comentários // dos exemplos são explicativos e saem da requisição real.

4. Por que ler em pacotes


Uma consulta aberta sobre uma tabela de movimento obriga o servidor a montar toda a resposta de uma vez, antes de enviar o primeiro registro. A tabela de itens de notas fiscais e de movimentos de estoque, MOVEQ2, tem mais de 200 colunas: algumas dezenas de milhares de linhas completas numa resposta só podem passar de uma centena de megabytes. O tempo de resposta cresce com o volume, e uma falha no meio obriga a recomeçar do zero. O mesmo vale para um filtro de período longo: um ano de notas numa resposta só pesa como a tabela inteira.

AspectoReleitura completa a cada cicloCarga incremental em pacotes
Volume por cicloA tabela inteira, sempre, e crescendo com o tempoSó o que foi incluído ou alterado desde o ciclo anterior
Tamanho de cada respostaProporcional à tabelaFixo, definido por você no limit
Falha no meioRecomeça do zeroRecomeça do último pacote gravado
Frequência possívelPoucas vezes ao dia, fora do horário de picoA cada poucos minutos
Registro antigo alteradoSó aparece na próxima releitura completaVolta na leitura seguinte, com a data nova

A diferença de volume é grande na prática. Numa base real de itens de nota, menos de 5% dos registros foram incluídos ou alterados em 30 dias, e menos de 0,5% nas últimas 24 horas. Nesse ritmo, numa tabela de 1 milhão de itens, reler tudo a cada 15 minutos traria 96 milhões de registros por dia; a carga incremental traria alguns milhares. Entre os itens alterados no mês havia itens de movimentos lançados meses antes: registros antigos que voltaram à fila e que uma leitura pela data de lançamento não traria de volta.

5. Como a data de atualização funciona


O comportamento é o mesmo em todas as tabelas que têm o controle:

Nem toda coluna de data é de atualização. Leia a descrição da coluna no dicionário antes de adotá-la como filtro. Colunas terminadas em _DATPRO costumam guardar a data de criação do registro, que não muda depois. Colunas *_STATUSDATA mudam só quando o status muda. As colunas de atualização aparecem no dicionário com descrições como Data da última alteração ou Data de Atualização do Cadastro, e os nomes variam de tabela para tabela: _DTATU, _DTMOD, _DHATU, _DATATU, entre outros.

6. Tabelas com o controle


As tabelas abaixo são as mais procuradas em integrações de leitura e têm a coluna de atualização preparada para consulta por faixa de data, que é exatamente a consulta desta página:

TabelaConteúdoColunaChave primária
MOVEQ2Itens das notas fiscais e das movimentações de estoqueME_DTATUFL_COD, CF_TIPO, FT_COD, ME_COD, ME_ITM, PD_COD
MOVEQCCusto dos itens de compraME_DTATUa mesma da MOVEQ2
CLIFOR1Cadastro de pessoas (clientes e fornecedores)CF_DTATUCF_COD
PRODCadastro de produtosPD_DATAPD_COD
PRODBARCODCódigos de barras dos produtosPD_DTMODPD_COD, PD_BARCOD

O mesmo controle existe em muitas outras tabelas do ERP. Para usar a estratégia numa tabela fora desta lista, confirme a coluna no dicionário e alinhe com o gestor do ERP Spalla o volume e o horário das cargas.

Os nomes da PROD fogem do padrão. A data de atualização do produto é PD_DATA, sem sufixo que a identifique. É o exemplo de por que a coluna se confirma pela descrição no dicionário e pelo comportamento (Passo 1), e não pelo nome.

7. Diagrama das três rotinas


A integração é organizada em três rotinas com frequências diferentes: a carga inicial (Passo 2) roda uma vez; a sincronização incremental (Passos 3 a 5) roda a cada poucos minutos; a reconciliação (Passo 6) roda com pouca frequência, fora do horário de pico.

Convenção visual: azul (Retaguarda do ERP) · verde (Via API) · cinza (no seu sistema).

8. Aviso sobre liberação na retaguarda


Atenção — a tabela precisa estar liberada para leitura no seu token.

Cada chave de API alcança uma lista própria de objetos, definida na retaguarda do ERP. Antes de começar:

Para cada tabela que a integração vai ler, anote três coisas no dicionário da sua instância:

O que anotarPor quê
A coluna de data de atualizaçãoÉ o filtro de todos os ciclos. Confirme pelo tipo (timestamp) e pela descrição, não só pelo nome (seção 5).
As colunas da chave primáriaSão a ordem da carga inicial e da reconciliação, e a chave da gravação no seu sistema.
As colunas que o seu sistema usaVão no fields. Numa tabela de centenas de colunas, pedir só as necessárias reduz a resposta várias vezes.

Confirme a coluna também pelo comportamento. A consulta da marca inicial (Passo 2) devolve a maior data da coluna: numa tabela em uso, ela é recente. Se a maior data for antiga numa tabela que você sabe que está em uso, a coluna não é a de atualização.

Os exemplos desta página usam a MOVEQ2, com a coluna ME_DTATU e a chave FL_COD, CF_TIPO, FT_COD, ME_COD, ME_ITM, PD_COD. Para outra tabela, troque os nomes pelos do dicionário.

A carga inicial (rotina 1) traz a tabela inteira uma única vez. Ela tem duas etapas, nesta ordem.

Primeiro, guarde a marca inicial: a maior data de atualização que existe na tabela antes de a carga começar. É dela que a sincronização parte, e é ela que garante que nada alterado durante a carga fique para trás.

{
    "method": "GET"
    ,"objectType": "TABLE"
    ,"objectName": "MOVEQ2"
    // Só a coluna de atualização
    ,"fields": ["ME_DTATU"]
    // A maior data primeiro
    ,"orderBy": ["ME_DTATU DESC"]
    ,"limit": 1
}

Como o pedido é de um registro só, a resposta é o próprio objeto (seção 3):

{
    "ME_DTATU": "2026-09-30T08:14:57.321"
}

Guarde esse valor como a marca, no formato de filtro: troque o T por espaço e descarte a fração de segundo — 2026-09-30 08:14:57. Descartar a fração deixa a marca um instante mais cedo, e nunca mais tarde, que é o lado seguro. Se o valor vier só com a data, acrescente 00:00:00. Se a tabela estiver vazia (resposta 404 com rowCount:0), use uma marca antiga, como 1900-01-01 00:00:00.

Depois, leia a tabela em pacotes, na ordem da chave primária, avançando o offset de 1 em 1:

{
    "method": "GET"
    ,"objectType": "TABLE"
    ,"objectName": "MOVEQ2"
    // Só as colunas que o seu sistema usa, com a chave e a data de atualização
    ,"fields": ["FL_COD", "CF_TIPO", "FT_COD", "ME_COD", "ME_ITM", "PD_COD", "ME_QTD", "ME_PRC", "ME_VALOR", "ME_DTATU"]
    // Ordem estável: a chave primária completa
    ,"orderBy": ["FL_COD ASC", "CF_TIPO ASC", "FT_COD ASC", "ME_COD ASC", "ME_ITM ASC", "PD_COD ASC"]
    // Tamanho do pacote
    ,"limit": 500
    // Número da página: 1, 2, 3...
    ,"offset": 1
}

Grave cada pacote no seu sistema (Passo 4) antes de pedir o próximo, e guarde junto a marca inicial e o número da última página gravada. Se o processo parar, retome da página seguinte com a mesma marca inicial, sem recalculá-la. A carga inicial termina quando vier um pacote incompleto.

Por que a carga inicial não usa a data de atualização. Em tabelas com histórico anterior ao controle, uma parte grande dos registros compartilha a mesma data e hora. Lida pela data, essa parte cairia inteira no caso do empate (Passo 3), página a página. A ordem da chave primária divide a tabela por igual.

Valores ilustrativos. As datas, as colunas escolhidas e o tamanho do pacote dos exemplos são ilustrativos. Os nomes reais das colunas estão no dicionário da sua instância.

Cada ciclo da sincronização (rotina 2) pede os registros com data de atualização igual ou posterior ao início do ciclo, em ordem de data. O início é a marca guardada menos uma margem de alguns minutos — por exemplo, 5 — explicada no Passo 5.

{
    "method": "GET"
    ,"objectType": "TABLE"
    ,"objectName": "MOVEQ2"
    ,"fields": ["FL_COD", "CF_TIPO", "FT_COD", "ME_COD", "ME_ITM", "PD_COD", "ME_QTD", "ME_PRC", "ME_VALOR", "ME_DTATU"]
    ,"where": [
        {
            // Início do ciclo: a marca menos a margem, com espaço entre a data e a hora
            "type": "expression"
            ,"value": "ME_DTATU >= '2026-09-30 08:09:57'"
        }
    ]
    // Só pela data de atualização
    ,"orderBy": ["ME_DTATU ASC"]
    // Tamanho do pacote; sem offset
    ,"limit": 500
}

Grave o pacote (Passo 4) e decida pelo tamanho da resposta:

Como o filtro é >= e o início perde a fração de segundo, os últimos registros de um pacote voltam no começo do seguinte. É de propósito: reler um registro não causa dano, porque a gravação é pela chave (Passo 4), e perder um registro causaria. Pela mesma razão, a ordem só precisa ser pela data: registros do mesmo segundo que ficaram na fronteira são relidos juntos no pacote seguinte.

Na sincronização, o pacote avança pela data, não pelo offset. Durante a leitura, os usuários continuam trabalhando no ERP. Um registro alterado entre um pacote e outro muda de posição na ordem de data, e a contagem por páginas passa a pular um registro que não mudou — em silêncio. Avançando pela data, cada pacote começa no valor em que o anterior terminou, e o que foi alterado no meio do caminho reaparece mais adiante.

Empate — pacote cheio em que todos os registros têm o mesmo segundo. Se a maior data do pacote, sem a fração de segundo, for igual ao início com que ele foi pedido, o próximo pacote pediria o mesmo segundo de novo, para sempre. Nesse caso, leia aquele segundo inteiro por páginas, na ordem da chave, e siga com o início no segundo seguinte:

{
    "method": "GET"
    ,"objectType": "TABLE"
    ,"objectName": "MOVEQ2"
    ,"fields": ["FL_COD", "CF_TIPO", "FT_COD", "ME_COD", "ME_ITM", "PD_COD", "ME_QTD", "ME_PRC", "ME_VALOR", "ME_DTATU"]
    ,"where": [
        {
            // Só o segundo do empate
            "type": "expression"
            ,"value": "ME_DTATU >= '2026-09-30 08:14:57' AND ME_DTATU < '2026-09-30 08:14:58'"
        }
    ]
    ,"orderBy": ["FL_COD ASC", "CF_TIPO ASC", "FT_COD ASC", "ME_COD ASC", "ME_ITM ASC", "PD_COD ASC"]
    ,"limit": 500
    // Aqui, sim, página a página: 1, 2, 3...
    ,"offset": 1
}

Calcule o segundo seguinte por aritmética de data (ele pode virar o dia). O empate acontece quando muitos registros são gravados de uma só vez; com o pacote de tamanho adequado, é a exceção do ciclo, não a regra.

O laço completo de um ciclo, em pseudo-código:

marca  = ler_marca("MOVEQ2")               // guardada no seu sistema, uma por tabela
inicio = marca - 5 minutos                 // margem (Passo 5)
repetir:
    linhas = GET where ME_DTATU >= inicio, orderBy ME_DTATU, limit N
    linhas = normalizar(linhas)            // objeto: lista de um item; 404 rowCount:0: lista vazia
    se linhas vazia: parar
    gravar_pela_chave(linhas)              // Passo 4
    maior  = truncar_segundo(max(ME_DTATU das linhas))
    guardar_marca("MOVEQ2", max(marca, maior))
    se quantidade(linhas) < N: parar      // pacote incompleto: ciclo encerrado
    se maior == inicio:                    // empate
        ler o segundo 'inicio' por páginas, na ordem da chave
        inicio = inicio + 1 segundo
    senão:
        inicio = maior

falha temporária (tempo esgotado, 5xx): repetir a mesma requisição, com espera crescente
400 ou 401: parar e registrar — não é temporário

Cada registro recebido, em qualquer das três rotinas, é incluído ou atualizado no seu sistema pela chave primária: se a chave já existe, atualize; se não existe, inclua. É essa gravação que torna inofensivas as releituras do Passo 3 e da retomada depois de uma falha.

Cabeçalhos dos movimentos. O cabeçalho (MOVEQ1, com a chave FL_COD, CF_TIPO, FT_COD, ME_COD) não tem coluna de atualização. Acompanhe-o pela data do movimento, ME_ENT, que é preparada para consulta por faixa: releia periodicamente — por exemplo, a cada hora — os cabeçalhos dos últimos dias, com as colunas que o seu sistema usa.

{
    "method": "GET"
    ,"objectType": "TABLE"
    ,"objectName": "MOVEQ1"
    // A chave, a indicação de cancelamento e as colunas que o seu sistema usa
    ,"fields": ["FL_COD", "CF_TIPO", "FT_COD", "ME_COD", "ME_ENT", "ME_CANCEL"]
    ,"where": [
        {
            // Movimentos dos últimos 7 dias
            "type": "expression"
            ,"value": "ME_ENT >= '2026-09-23 00:00:00'"
        }
    ]
    ,"orderBy": ["FL_COD ASC", "CF_TIPO ASC", "FT_COD ASC", "ME_COD ASC"]
    ,"limit": 500
    ,"offset": 1
}

Nota cancelada. O cancelamento de uma nota fiscal remove os itens dela da MOVEQ2 e marca o cabeçalho com ME_CANCEL = 1. Os itens não voltam pela fila, porque deixaram de existir. Na releitura dos cabeçalhos, remova do seu sistema os itens das notas com ME_CANCEL = 1. Alterações em cabeçalhos mais antigos que a janela da releitura ficam para a reconciliação completa (Passo 6).

A integração guarda uma marca por tabela (e, durante a carga inicial, também a última página gravada). Quatro regras a mantêm confiável:

RegraPor quê
Grave a marca só depois do pacoteA marca avança quando os registros do pacote já estão gravados no seu sistema. Se o processo cair no meio, o ciclo seguinte relê o último pacote em vez de pulá-lo.
A marca vem dos dados, não do relógioUse a maior data de atualização recebida, nunca a hora do seu servidor. O relógio da sua máquina e o do servidor do ERP não são o mesmo, e a diferença vira registro perdido.
Grave a marca sem a margemA margem entra só no início da leitura (Passo 3). Gravada na marca, ela recuaria a cada ciclo.
Comece cada ciclo com a margemA data é marcada no início da gravação do registro, e ele só aparece na leitura quando a gravação termina. Numa gravação demorada, um registro pode surgir com data um pouco anterior à da última leitura. A margem de alguns minutos o alcança, e a gravação pela chave descarta a repetição. Ela precisa ser maior que a gravação mais longa da tabela; o que escapar dela, a reconciliação completa corrige.

Com a marca e a margem, o intervalo entre os ciclos é decisão do seu negócio. A cada 5 a 15 minutos é o ponto de partida comum: os ciclos ficam curtos e a sua base fica poucos minutos atrás do ERP.

A reconciliação (rotina 3) faz parte da integração, e não é opcional. Ela cobre o que a data de atualização não alcança — os registros excluídos no ERP — e qualquer divergência que tenha escapado dos ciclos, como uma falha de gravação no seu próprio sistema.

Reconciliação de chaves. Leia só as colunas da chave, em pacotes, na ordem da chave. Sem as demais colunas, cada registro ocupa poucos bytes e o pacote pode ser bem maior:

{
    "method": "GET"
    ,"objectType": "TABLE"
    ,"objectName": "MOVEQ2"
    // Só a chave primária
    ,"fields": ["FL_COD", "CF_TIPO", "FT_COD", "ME_COD", "ME_ITM", "PD_COD"]
    ,"orderBy": ["FL_COD ASC", "CF_TIPO ASC", "FT_COD ASC", "ME_COD ASC", "ME_ITM ASC", "PD_COD ASC"]
    ,"limit": 5000
    ,"offset": 1
}

Compare as chaves recebidas com as do seu sistema nos dois sentidos:

DivergênciaO que fazer
A chave está no seu sistema e não veio do ERPConfirme com uma consulta pela chave completa, com "type": "field" em cada coluna. Se a resposta for 404 com rowCount:0, o registro foi excluído: remova-o do seu lado.
A chave veio do ERP e não está no seu sistemaBusque o registro pela chave e inclua-o.

A confirmação antes de remover importa: enquanto a reconciliação lê as páginas, o ERP continua em uso, e uma exclusão no meio do caminho desloca as páginas seguintes. A comparação nos dois sentidos também alcança o registro que uma carga inicial tenha deixado para trás pelo mesmo motivo — por isso vale rodá-la logo depois da carga inicial, e depois uma vez por dia, fora do horário de pico.

Reconciliação completa. De tempos em tempos — semanal ou mensal, conforme o volume — repita a leitura da carga inicial (Passo 2) comparando os valores com os do seu sistema. Ela garante que a sua base e o ERP continuam iguais.

Boas práticas


Resultados inesperados e erros


SituaçãoCausaO que fazer
Consulta demorada ou resposta muito grandeConsulta sem limit, com pacote grande demais ou com todas as colunasDeclarar o limit, reduzir o pacote e restringir o fields.
404 — ... [ rowCount:0 ]Nenhum registro atende ao filtroTratar como pacote vazio: fim da leitura, não erro.
Contagem de registros errada com um único registroUm registro chega como objeto, não como listaNormalizar a resposta para lista antes de contar.
400 no filtro de dataData com T ou sem a hora no whereUsar 'AAAA-MM-DD HH:MM:SS', com espaço.
Filtro de data sem nenhum registro, com dados existentesComparação por = contra uma data que tem fração de segundoComparar com >=.
O ciclo pede o mesmo pacote sem pararEmpate: pacote cheio com todos os registros no mesmo segundoLer aquele segundo por páginas, na ordem da chave (Passo 3).
Registros faltando na carga inicialoffset tratado como quantidade de registros a pular, ou páginas sem orderByAvançar o offset de 1 em 1, com orderBy pela chave, e reconciliar nos dois sentidos (Passo 6).
Itens de nota cancelada continuam no seu sistemaO cancelamento remove os itens, e a exclusão não gera data de atualizaçãoReler os cabeçalhos recentes e remover os itens das notas com ME_CANCEL = 1 (Passo 4).
Registro excluído no ERP continua no seu sistemaA exclusão não gera data de atualizaçãoReconciliar pelas chaves (Passo 6).
401 com a mensagem contendo nao esta listado nas opcoes desta integracaoTabela não liberada no tokenPedir a liberação ao gestor do ERP Spalla (seção 8).

Spalla API Toolkit — deixe a sua IA montar a integração do jeito certo


Se você usa um agente de inteligência artificial para escrever ou operar a integração, o Spalla API Toolkit ensina a ele esta estratégia e as regras da API de que ela depende. Com o toolkit instalado, o agente:

Como obter: o toolkit é distribuído de forma controlada. Fale com o gestor do ERP Spalla da sua empresa, que também fornece o endereço da API e o userToken da integração. Recursos e vantagens na documentação geral da API.

Dica — identificar a tabela e o campo a partir da interface do ERP


Atalho para inspeção visual. Ao operar uma tela diretamente pela interface do ERP, pressione Ctrl + Alt + Shift + H. O sistema altera o título da janela indicando que o modo de identificação está ativo. A partir daí, ao parar o mouse sobre qualquer campo da tela, aparece um hintbox mostrando o nome da tabela e do campo aos quais ele está vinculado. É a forma mais rápida de descobrir em que tabela está a informação que o seu sistema precisa — e, com ela, conferir no dicionário se a tabela tem a coluna de atualização.

Relação com os demais guias


Onde aprofundar


Os nomes de tabela, de coluna e da chave primária de cada objeto estão no dicionário oficial da sua instância (seção 2), que é a fonte autoritativa. A montagem da requisição — URL base, porta, autenticação, os parâmetros do GET e o formato dos valores — está na documentação geral da API.