Boas práticas de uso da API
Esta página descreve como consumir a API idworks de forma que a sua integração seja estável para você e educada com a plataforma — que é compartilhada com as telas do produto que a sua própria equipe usa. Ela aprofunda pontos que Começando apresenta de forma resumida; onde houver sobreposição, o link aponta para lá em vez de repetir.
Quase tudo aqui é recomendação: o objetivo é a sua integração ficar estável. A primeira seção é a exceção — ela é regra de uso, e vale independentemente de o seu volume estar dentro do rate limit.
Tudo o que está aqui foi conferido contra o comportamento real da API. Quando uma recomendação é genérica de boa engenharia (e não uma garantia do idworks), o texto diz isso explicitamente.
Índice
Regra de uso
Volume e ritmo
Confiabilidade
Operação
Varredura completa da base
:::danger Varrer a base completa de forma repetitiva não é permitido
Por questão de segurança, não é permitido percorrer a base inteira de um
recurso em ciclo — paginar até o fim de GET /orders, GET /sku,
GET /consumer ou de qualquer outra listagem e repetir isso de hora em hora,
todo dia ou toda semana.
:::
Esta regra não é sobre ritmo. Um job lento, com uma requisição por segundo e sem nunca tomar um 429, continua sendo varredura completa repetitiva se o resultado é levar a base inteira embora a cada rodada. O que está em questão é o padrão de acesso: uma extração integral em laço é indistinguível de uma cópia não autorizada do cadastro da empresa, e é exatamente isso que a regra existe para impedir.
Vale também o inverso: respeitar o limite de requisições não autoriza a varredura. As duas coisas são independentes.
O que fazer no lugar
| Necessidade | Caminho correto |
|---|---|
| Manter uma cópia da base atualizada | Sincronização incremental com os parâmetros Since* |
| Reagir a uma mudança assim que ela acontece | Webhook |
| Buscar registros específicos | Filtros do endpoint — id, número do pedido, data, CSV de ids |
| Relatório ou fechamento de um período | DateFrom/DateTo na janela do período, e só |
| Carga inicial da integração | Uma vez só, e depois incremental (ver abaixo) |
| Extração integral recorrente por exigência de negócio | Fale com o suporte antes de implementar |
A carga inicial é a única varredura esperada
Toda integração precisa começar de algum lugar, e para isso percorrer o histórico é legítimo — uma vez. Terminada a carga, guarde a marca d'água (ver O padrão de marca d'água) e passe a puxar apenas o delta. Refazer a carga inicial a cada ciclo, no lugar de sincronizar, é precisamente o que esta seção proíbe.
Se a sua integração perdeu a marca d'água e precisa recarregar, isso é um evento excepcional — não a rotina. E se você acha que o seu caso exige extração integral periódica, trate com o suporte antes de colocar em produção, em vez de descobrir o problema depois.
:::note O acesso é registrado Toda requisição fica registrada com usuário, IP de origem, rota e horário. O padrão de acesso de uma integração é visível — inclusive o de um laço de varredura que "não incomoda ninguém" por ser lento. :::
Consumo responsável
O limite publicado é de 5 requisições/segundo por endpoint (ver Rate limits). Vale conhecer a mecânica por trás dele, porque ela muda o desenho da sua integração:
- A janela é curta, não é uma média por minuto. O contador trabalha em janela de poucos segundos. Uma rajada de 30 chamadas "de uma vez" estoura, mesmo que o seu volume da hora inteira seja baixo.
- Estourar custa mais que uma rejeição. Depois de ultrapassar o limite, o balde entra em bloqueio por alguns segundos — as chamadas seguintes daquele balde são recusadas mesmo que você já tenha parado de enviar. Insistir durante o bloqueio só prolonga o problema.
- O balde é por rota, com os parâmetros de caminho agrupados.
GET /orders/{idorder}é um único balde, não um por pedido: buscar 50 pedidos por id em paralelo concorre tudo no mesmo contador. - Rotas diferentes têm baldes independentes. Ler
GET /orderseGET /skuao mesmo tempo não soma no mesmo limite. Por isso, quando você precisa de vazão, o ganho vem de paralelizar entre recursos diferentes — nunca de abrir mais conexões contra a mesma rota. - O balde acompanha o usuário do token, não a empresa. Se a integração usa o mesmo login que uma pessoa usa nas telas, os dois disputam o mesmo contador e cada um vira causa de 429 do outro. Crie um usuário dedicado para a integração (ver Segurança do token).
Como desenhar o ritmo
- Serialize por rota. Uma fila com concorrência 1 ou 2 por rota é suficiente para o limite documentado e é muito mais simples de operar do que um pool grande com retentativa agressiva.
- Espalhe o trabalho no tempo. Cargas diárias (preços, catálogo, saldo) ganham mais em rodar espaçadas do que em terminar cedo. Um job que leva 20 minutos sem nenhum 429 é melhor que um que leva 4 e derruba a integração.
- Não calibre "por observação". Quando o mecanismo de contagem fica indisponível, a API libera a requisição em vez de recusá-la. Isso é bom para você — mas significa que uma janela sem 429 não prova que você está dentro do limite. Dimensione pelo limite publicado, não pelo que passou.
- O 429 que você vai encontrar na prática vem das leituras (
GET). Não trate a ausência de 429 em escrita como licença para rajada: escrita é mais cara, tem efeito colateral e cada repetição indevida vira registro duplicado (ver Repetir requisições com segurança).
O que fazer quando receber 429
O 429 Too Many Requests é emitido antes do handler do endpoint — a sua
requisição não chegou a ser processada. Nenhum dado foi lido ou gravado.
Faça:
-
Pare de enviar naquela rota. Não é a hora de tentar outro caminho para o mesmo dado; é a hora de esperar.
-
Espere pelo menos alguns segundos antes da primeira retentativa. O bloqueio dura mais que a janela do contador — retentar em 200 ms garante um segundo 429.
-
Use backoff exponencial com jitter. Dobre a espera a cada falha e acrescente uma variação aleatória, para que várias tarefas suas não voltem todas no mesmo instante:
// backoff exponencial com jitter, teto de 60 sconst base = 5000; // 1ª espera: 5 sconst teto = 60000;for (let tentativa = 0; tentativa < 6; tentativa++) {const resp = await fetch(url, { headers });if (resp.status !== 429) return resp;const retryAfter = Number(resp.headers.get('Retry-After')) * 1000;const exponencial = Math.min(base * 2 ** tentativa, teto);const espera = (retryAfter > 0 ? retryAfter : exponencial)* (0.5 + Math.random()); // jitterawait new Promise(r => setTimeout(r, espera));}throw new Error('429 persistente — revise a concorrência da rota'); -
Leia o
Retry-Afterse ele vier, mas tenha fallback. A referência documenta o header na resposta de 429 (Rate limits); trate-o como opcional. Um cliente que só sabe esperar quando o header existe quebra no dia em que ele não vier. -
Tenha um teto de tentativas e um alarme. 429 recorrente não é ruído de rede: é a sua integração pedindo mais vazão do que existe. Depois de N tentativas, pare a tarefa e avise alguém.
Não faça:
- ❌ Retentar imediatamente em laço — o caminho mais rápido para ficar permanentemente bloqueado naquela rota.
- ❌ Abrir mais threads/workers "para compensar a lentidão" — todos caem no mesmo balde e a vazão total cai.
- ❌ Distribuir a mesma carga entre vários usuários para multiplicar o limite. Isso é contornar o limite, não respeitá-lo, e o efeito na plataforma é o mesmo de um cliente único abusivo.
- ❌ Tratar 429 como erro fatal e abortar a sincronização inteira. É um sinal de ritmo, e o único tratamento correto é esperar e continuar de onde parou.
Paginação e volume
Todas as listagens paginam pelo parâmetro Page. Três detalhes que
costumam pegar quem está começando:
- Não existe parâmetro de tamanho de página. O tamanho é fixo por
endpoint e varia bastante entre eles — de 500 a alguns milhares de itens.
A descrição do
Pagede cada operação informa o valor exato (por exemplo:GET /ordersusa blocos de 2000,GET /skublocos de 500,GET /job/{idjob}blocos de 5000). - A indexação normalmente começa em
0, mas nem sempre. Algumas listagens são 1-based (é o caso deGET /sku/batch,GET /sku/location,GET /charge/bank-slipeGET /purchase/buy-order). Leia a descrição doPagedo endpoint que você usa em vez de assumir — começar na página errada faz você pular ou repetir um bloco inteiro em silêncio. - O fim da lista é uma página incompleta. Pare quando a página voltar com menos itens que o tamanho do bloco (ou vazia).
Prefira cursor a offset em volumes grandes
Paginação por offset fica progressivamente mais cara conforme você avança, e é frágil quando registros são criados durante a varredura. Alguns endpoints já oferecem paginação por chave — use quando existir:
GET /ordersaceitaLastID. Informe oIDOrderdo último item da página anterior e a listagem ignora o offset, devolvendo o bloco seguinte a partir daquela chave.GET /skuaceitaSinceIDSku, que devolve apenas SKUs comIDSkumaior ou igual ao informado.
Filtre antes de paginar
Antes de percorrer páginas, restrinja. Não é só eficiência: percorrer tudo em ciclo esbarra na regra de Varredura completa da base.
- Por data. A maioria das listagens aceita
DateFrom/DateTo. Repare que o campo por trás do filtro muda conforme o endpoint (data de criação emGET /orders, data de emissão da NF emGET /purchase/feed, validade do lote emGET /sku/batch), e que o limite superior costuma incluir o dia inteiro (23:59:59). - Por janelas menores. Um mês inteiro em uma consulta produz muitas páginas e uma resposta pesada. Janelas de um dia (ou de algumas horas, em contas de alto volume) são mais rápidas, mais fáceis de retomar e menos suscetíveis a timeout.
- Por chave de negócio, quando você já sabe o que procura.
GET /ordersaceita CSV em vários filtros (IDOrder=1234,5678,Order=OT-1234,ML-5648,NfeNumber=...): uma chamada com vários ids é sempre melhor que N chamadas de um id, tanto para você quanto para o balde de rate limit.
:::caution A busca rápida não serve para varredura
O parâmetro Search de GET /orders é uma busca de tela: ele é limitado
aos últimos 3 meses e casa contra vários campos por prefixo. É ótimo para
"achar aquele pedido" e péssimo como base de sincronização — para isso use os
filtros explícitos e os parâmetros incrementais da próxima seção.
:::
Cargas em massa vão por job, não por laço
Para escrita em volume existe o fluxo assíncrono de ação em massa: POST /job envia uma planilha (com upload direto ou em dois passos por URL
temporária, o caminho recomendado para arquivos grandes) e devolve um IDJob;
o resultado linha a linha é acompanhado em GET /job/{idjob}. Isso é
preferível a milhares de POST individuais: uma chamada, um acompanhamento, e
o processamento acontece fora do caminho síncrono da API.
Sincronização incremental
Esta é a resposta à regra de Varredura completa da base: em vez de reler tudo, releia só o que mudou. Vários endpoints expõem parâmetros feitos exatamente para isso:
| Parâmetro | O que faz | Onde existe (verificado) |
|---|---|---|
SinceDateLastRecordModification | Registros com DateLastRecordModification ≥ o timestamp informado — captura criação e alteração | GET /sku, GET /sku/pricing, GET /sku/balance, GET /sku/movement, GET /orders, GET /consumer, GET /purchase |
SinceRecordTimestamp | Registros criados a partir do timestamp | GET /orders, GET /sku, GET /purchase |
SinceIDSku / LastID | Cursor por chave, para percorrer volume sem offset | GET /sku / GET /orders |
O campo DateLastRecordModification também vem na resposta das listagens
correspondentes, então o ciclo fecha sozinho.
O padrão de marca d'água
- Guarde, do seu lado, o maior
DateLastRecordModificationque você processou com sucesso naquele recurso. - Na rodada seguinte, consulte com
SinceDateLastRecordModificationigual a essa marca menos uma folga de alguns minutos. - Processe as páginas, atualize a marca só no fim, quando tudo tiver sido gravado do seu lado.
A folga do passo 2 não é preciosismo: ela cobre diferença de relógio e
registros gravados no exato instante do corte. O preço é reprocessar alguns
registros — por isso o seu lado precisa ser idempotente por chave (IDOrder,
IDSku), tratando o reprocessamento como upsert.
E atualize a marca apenas depois do commit local. Avançar a marca antes de gravar transforma qualquer falha no meio do lote em buraco permanente na sua base: aqueles registros não voltam a aparecer na próxima janela.
:::tip Nem todo recurso tem filtro incremental
Onde não houver Since*, use DateFrom/DateTo em janelas móveis curtas —
por exemplo, reler sempre os últimos 7 dias. Isso captura correção retroativa
sem reler o histórico, e é o substituto correto do Since* onde ele não existe.
Uma releitura integral continua sujeita à regra de
Varredura completa da base.
:::
Repetir requisições com segurança
A API não tem chave de idempotência. Não existe header do tipo
Idempotency-Key: repetir um POST que já foi processado cria outro
registro. Um POST /orders repetido gera um segundo pedido, com outro
IDOrder, e nada no idworks vai desfazer isso por você.
Como classificar as operações:
| Operação | Repetir é seguro? | Observação |
|---|---|---|
GET | Sim | Sem efeito colateral. Retente à vontade, com backoff. |
POST (cria recurso) | Não | Cada chamada bem-sucedida cria um registro novo. |
PUT (atualiza por id) | Em geral sim | Endereça um recurso existente e converge para o mesmo estado final. Confira a descrição do endpoint. |
DELETE (por id) | Em geral sim | A repetição costuma falhar com "não encontrado", o que é inofensivo. |
Alguns endpoints têm proteção própria e dizem isso na documentação da
operação — por exemplo, POST /job reaproveita um job em processamento com o
mesmo arquivo em vez de disparar outro, e POST /store-front/cashier/{idbankaccount}/open retoma a sessão existente quando é o
mesmo usuário. São exceções documentadas caso a caso, não uma regra da API:
na dúvida, assuma que POST duplica.
Quando a resposta se perde
Timeout de rede, conexão cortada, processo derrubado no meio — você não sabe se
o POST foi aplicado. Nunca reenvie às cegas. O procedimento é:
- Consulte antes de reenviar, usando um identificador que você controla
e enviou na criação. Para pedidos,
GET /ordersfiltra porOrder(número do pedido) e porOrderFrom(número no canal de origem) — os dois aceitam CSV. CombinarDateFromcomIDConsumertambém isola bem a janela. - Se o registro existir, considere a operação concluída e siga em frente.
- Se não existir, aí sim reenvie.
Isso só funciona se você gerar a chave de negócio antes de chamar a API e persistir a intenção do seu lado antes do envio. Um número de pedido determinístico gerado pelo seu sistema é o que transforma "não sei se criou" em uma pergunta que a API sabe responder.
Vale também registrar, junto com a intenção, o resultado de cada tentativa (status HTTP e corpo). É esse par intenção/resultado que permite reconciliar depois — e é o que o suporte vai pedir quando você abrir um chamado.
Webhooks e polling
O idworks envia webhooks para eventos de produto, anúncio, pedido, transporte, fiscal e logística. Dois pontos que mudam o desenho da integração:
- A configuração é feita no produto, não pela API. Endpoint, cabeçalho de
autenticação e quais tópicos disparam ficam em Configurações →
Parametrizações → Webhook. A API expõe apenas
GET /webhook, que lista o histórico de envios (tópico, endpoint, status, tentativas, payload enviado e resposta recebida). Detalhes de configuração e a lista de tópicos estão no artigo Webhook da Central de Ajuda. - O webhook avisa; quem busca o dado é você. O payload traz o tópico, a conta, o momento da modificação e os identificadores do recurso afetado com uma URL relativa para consulta. Ou seja: o webhook não elimina as chamadas de leitura — ele elimina o laço de polling.
O que o seu endpoint precisa garantir
- Responder
2xxrápido. O timeout de envio é de 10 segundos. Passou disso, a entrega é marcada como erro e reenfileirada, mesmo que você tenha processado tudo. Aceite, responda2xx, processe em background. - Ser idempotente. A entrega é at-least-once: falhas voltam para a fila e
são retentadas, e o campo Tentativas do log conta cada rodada. O mesmo
evento pode chegar mais de uma vez — deduplique pelo identificador do recurso
e pelo
ModificationTimestamp. - Tolerar reordenação. Nada garante que dois eventos do mesmo recurso cheguem na ordem em que aconteceram. Compare o timestamp do evento com o que você já gravou antes de sobrescrever.
- Voltar sozinho. Enquanto o seu endpoint estiver fora, as mensagens são retentadas. Não há painel de reprocessamento manual: uma mensagem que esgotou a fila só volta se o evento for gerado de novo no idworks.
Escolhendo entre os dois
| Situação | Use |
|---|---|
| Precisa reagir em segundos a um evento (status de pedido, tracking, emissão de NF) | Webhook |
| Precisa de garantia de completude no fim do dia | Polling incremental (Since*) |
| Recurso sem tópico de webhook | Polling incremental |
| Integração crítica | Os dois — webhook para latência, polling como rede de segurança |
A recomendação para integração crítica é a mais importante: webhook é entrega
best-effort at-least-once, e uma janela em que o seu endpoint esteve fora
pode custar eventos. Uma reconciliação incremental periódica, com
SinceDateLastRecordModification, custa pouquíssimas chamadas e fecha esse
buraco. O log de GET /webhook (filtrável por Topic, Status e faixa de
data) é a ferramenta para auditar o que realmente chegou.
Segurança do token
O token JWT vem do login e viaja no header Authorization
(ver Autenticação). O que isso implica na prática:
- O token expira. A validade padrão é de 14 horas, e o campo
expireinhourdo login permite pedir outra (ex.:12h,7d). Trate a renovação como parte do cliente: obtenha um token, guarde em memória, e renove antes de expirar em vez de esperar o primeiro401. Não peça um token novo a cada requisição — isso é desperdício e transforma o login em ponto único de falha da integração. - Cuidado com o bloqueio por tentativa falha. Três tentativas de login malsucedidas bloqueiam o usuário por 10 minutos. Um laço de reautenticação com credencial errada tranca a integração inteira.
- Não versione a credencial. Senha do usuário de integração e tokens fora do repositório, sempre — variável de ambiente ou cofre de segredos. Um JWT commitado vale até expirar, e quem tem o repositório tem a conta.
- Não exponha o token no front-end. Qualquer token que chega ao navegador é visível para o usuário e para qualquer extensão instalada. Integração server-to-server: o token nunca sai do seu backend.
- Prefira o header à query string. A API também aceita o token no parâmetro
token, e o autorizador tem o cuidado de não registrá-lo nos próprios logs — mas a query string ainda atravessa proxies, histórico de navegador e logs de terceiros que você não controla. UseAuthorization. - O token é preso ao subdomínio. Ele só vale para a conta cujo
AccountNameestá na URL (ver URL base). Um token da contaacmeenviado parahttps://outra.api-idworks.com.br/é recusado — não é uma credencial global.
Usuário dedicado e rotação
Crie um usuário exclusivo para a integração — não reaproveite o login de uma pessoa. Isso dá três coisas de uma vez:
- Privilégio mínimo. Configure um perfil em Configurações → Perfis de Acesso com apenas o que a integração precisa. Menos privilégio, menos estrago se o token vazar.
- Rastreabilidade. As chamadas da integração ficam separadas das de pessoas nos logs — o que muda completamente a conversa com o suporte.
- Balde de rate limit próprio. A integração deixa de disputar contador com quem está usando as telas.
Para rotacionar, autentique de novo e passe a usar o token novo — os antigos caducam sozinhos no prazo de expiração. E, se a credencial vazou, desativar o usuário no produto invalida também os tokens já emitidos para ele, sem esperar a expiração — a revogação leva até cerca de um minuto para valer em toda a API. Esse é o botão de emergência.
Erros e diagnóstico
O corpo de erro segue sempre o mesmo formato:
{ "errorMessage": "[BadRequest] - Consumidor não cadastrado" }
O prefixo é a parte que a sua integração deve ler. As mensagens em si mudam com o tempo e com o idioma do cadastro; o prefixo é a classificação estável:
| Prefixo | Status | Significado | Retentar? |
|---|---|---|---|
[BadRequest] - ... | 400 | Erro de validação ou de regra de negócio: campo faltando, cadastro inexistente, operação não permitida no estado atual | Não. Repetir o mesmo payload dá o mesmo erro — corrija os dados. |
[NotFound] ... | 404 | O recurso não existe ou não pertence à conta autenticada | Não sem mudar o identificador. |
Error: ... | 500 | Falha interna, não classificada como erro do cliente | Sim, com backoff. |
Outros prefixos entre colchetes aparecem em situações específicas — por
exemplo, [Forbidden] - Login temporariamente bloqueado... no login. A regra é
a mesma para todos: classifique pelo prefixo, não pela frase.
Alguns erros trazem campos extras: errorCode (código do tipo de erro) e
errorHelp (link para o artigo que explica aquele erro). Quando vierem,
guarde-os no seu log — são o caminho mais curto para a causa.
Os códigos 401 e 403 vêm do autorizador, antes do handler, e estão
descritos em
Erros de autenticação e autorização.
Um sintoma vale destacar: 403 em todas as rotas ao mesmo tempo, incluindo
as que funcionavam há minutos, normalmente é janela de manutenção, não
mudança de privilégio. Espere e repita antes de investigar perfis de acesso.
Registre o suficiente para depurar
Guarde, para cada chamada que falhar: método e rota, query string (sem o token), corpo enviado, status HTTP, corpo recebido e o instante da requisição com fuso horário. Sem isso, um erro intermitente é irreprodutível.
Como abrir um chamado útil
O lado idworks registra, por requisição, o usuário, o IP de origem, a rota e a query string (com credenciais e documentos removidos). Para que o suporte encontre a sua chamada no meio disso, informe:
- Subdomínio da conta (
AccountName) e login do usuário do token; - Método e rota exata —
POST /orders, não "o endpoint de pedidos"; - Data, hora e fuso da requisição, o mais preciso possível;
- Corpo enviado e resposta completa, incluindo o
errorMessageliteral e oerrorCode, se houver; - O que você esperava que acontecesse;
- Se é reproduzível e com que frequência.
Nunca inclua o token, senha ou documento do titular no chamado. Se o problema for de webhook, informe também o ID Webhook Log da linha em Configurações → Webhook — ele identifica a mensagem exata.
Antes de apontar para produção
A conta pública teste é o ponto de partida: ela é o valor padrão da
AccountName na collection do Postman e no spec, e serve para você conferir
formato de payload, nomes de campo e forma das respostas antes de escrever
código contra a sua conta.
:::warning teste é uma conta de demonstração, não um ambiente separado
Não existe um ambiente de sandbox isolado: a conta teste é uma conta
pública compartilhada, sobre a mesma infraestrutura. Consequências práticas:
- Nunca envie dado real (cliente, documento, valor) para ela.
- Não conte com o que você gravou lá. Outras pessoas usam a mesma conta.
- Não use para teste de carga. Você está consumindo capacidade compartilhada e o resultado não representa a sua conta.
Para exercitar fluxos de escrita com dados seus, faça isso na sua própria conta, em uma empresa de teste, e combine com o suporte antes se o fluxo tiver efeito fiscal ou financeiro. :::
Roteiro sugerido antes de virar a chave:
- Leitura primeiro. Valide os
GETque a sua integração usa, com filtros e paginação reais, e confirme que você chega ao fim das listas corretamente. - Escrita em volume baixo. Um pedido, um SKU, uma atualização — e confira o resultado pelas telas do produto, não só pela resposta da API.
- Simule falha. Derrube o seu processo no meio de um
POSTe exercite a reconciliação da seção Repetir requisições com segurança. É o caminho que mais dói descobrir em produção. - Ligue o rate limit no seu lado. Confirme que a sua fila respeita a concorrência que você desenhou e que o backoff realmente espera.
- Só então troque o
AccountNamepara a conta de produção.
Checklist de integração
- Concorrência limitada por rota, não global
- Backoff exponencial com jitter e teto de tentativas para
429e5xx -
Retry-Afterlido quando presente, com fallback fixo - Retentativa apenas em
GET,5xxe timeouts — nunca em[BadRequest] -
POSTprotegido por consulta prévia com chave de negócio própria - Nenhuma varredura integral em ciclo — carga inicial só uma vez
- Sincronização por
SinceDateLastRecordModificationcom marca d'água e folga - Marca d'água avançada só depois do commit local
- Paginação lendo o tamanho de bloco e a base (
0ou1) do endpoint usado - Cursor (
LastID,SinceIDSku) preferido a offset em volume grande - Endpoint de webhook idempotente, respondendo
2xxem menos de 10 s - Reconciliação incremental periódica como rede de segurança do webhook
- Usuário dedicado, com privilégio mínimo, e credencial fora do repositório
- Token renovado antes de expirar, nunca a cada requisição
- Log de método, rota, payload, status e horário para toda falha