Pular para o conteúdo principal

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

NecessidadeCaminho correto
Manter uma cópia da base atualizadaSincronização incremental com os parâmetros Since*
Reagir a uma mudança assim que ela aconteceWebhook
Buscar registros específicosFiltros do endpoint — id, número do pedido, data, CSV de ids
Relatório ou fechamento de um períodoDateFrom/DateTo na janela do período, e só
Carga inicial da integraçãoUma vez só, e depois incremental (ver abaixo)
Extração integral recorrente por exigência de negócioFale 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 /orders e GET /sku ao 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:

  1. Pare de enviar naquela rota. Não é a hora de tentar outro caminho para o mesmo dado; é a hora de esperar.

  2. 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.

  3. 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 s
    const base = 5000; // 1ª espera: 5 s
    const 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()); // jitter
    await new Promise(r => setTimeout(r, espera));
    }
    throw new Error('429 persistente — revise a concorrência da rota');
  4. Leia o Retry-After se 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.

  5. 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 Page de cada operação informa o valor exato (por exemplo: GET /orders usa blocos de 2000, GET /sku blocos 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 de GET /sku/batch, GET /sku/location, GET /charge/bank-slip e GET /purchase/buy-order). Leia a descrição do Page do 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 /orders aceita LastID. Informe o IDOrder do último item da página anterior e a listagem ignora o offset, devolvendo o bloco seguinte a partir daquela chave.
  • GET /sku aceita SinceIDSku, que devolve apenas SKUs com IDSku maior 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 em GET /orders, data de emissão da NF em GET /purchase/feed, validade do lote em GET /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 /orders aceita 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âmetroO que fazOnde existe (verificado)
SinceDateLastRecordModificationRegistros com DateLastRecordModification ≥ o timestamp informado — captura criação e alteraçãoGET /sku, GET /sku/pricing, GET /sku/balance, GET /sku/movement, GET /orders, GET /consumer, GET /purchase
SinceRecordTimestampRegistros criados a partir do timestampGET /orders, GET /sku, GET /purchase
SinceIDSku / LastIDCursor por chave, para percorrer volume sem offsetGET /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

  1. Guarde, do seu lado, o maior DateLastRecordModification que você processou com sucesso naquele recurso.
  2. Na rodada seguinte, consulte com SinceDateLastRecordModification igual a essa marca menos uma folga de alguns minutos.
  3. 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çãoRepetir é seguro?Observação
GETSimSem efeito colateral. Retente à vontade, com backoff.
POST (cria recurso)NãoCada chamada bem-sucedida cria um registro novo.
PUT (atualiza por id)Em geral simEndereça um recurso existente e converge para o mesmo estado final. Confira a descrição do endpoint.
DELETE (por id)Em geral simA 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 é:

  1. Consulte antes de reenviar, usando um identificador que você controla e enviou na criação. Para pedidos, GET /orders filtra por Order (número do pedido) e por OrderFrom (número no canal de origem) — os dois aceitam CSV. Combinar DateFrom com IDConsumer também isola bem a janela.
  2. Se o registro existir, considere a operação concluída e siga em frente.
  3. 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 2xx rápido. O timeout de envio é de 10 segundos. Passou disso, a entrega é marcada como erro e reenfileirada, mesmo que você tenha processado tudo. Aceite, responda 2xx, 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çãoUse
Precisa reagir em segundos a um evento (status de pedido, tracking, emissão de NF)Webhook
Precisa de garantia de completude no fim do diaPolling incremental (Since*)
Recurso sem tópico de webhookPolling incremental
Integração críticaOs 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 expireinhour do 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 primeiro 401. 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. Use Authorization.
  • O token é preso ao subdomínio. Ele só vale para a conta cujo AccountName está na URL (ver URL base). Um token da conta acme enviado para https://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:

  1. 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.
  2. Rastreabilidade. As chamadas da integração ficam separadas das de pessoas nos logs — o que muda completamente a conversa com o suporte.
  3. 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:

PrefixoStatusSignificadoRetentar?
[BadRequest] - ...400Erro de validação ou de regra de negócio: campo faltando, cadastro inexistente, operação não permitida no estado atualNão. Repetir o mesmo payload dá o mesmo erro — corrija os dados.
[NotFound] ...404O recurso não existe ou não pertence à conta autenticadaNão sem mudar o identificador.
Error: ...500Falha interna, não classificada como erro do clienteSim, 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 exataPOST /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 errorMessage literal e o errorCode, 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:

  1. Leitura primeiro. Valide os GET que a sua integração usa, com filtros e paginação reais, e confirme que você chega ao fim das listas corretamente.
  2. 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.
  3. Simule falha. Derrube o seu processo no meio de um POST e exercite a reconciliação da seção Repetir requisições com segurança. É o caminho que mais dói descobrir em produção.
  4. 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.
  5. Só então troque o AccountName para 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 429 e 5xx
  • Retry-After lido quando presente, com fallback fixo
  • Retentativa apenas em GET, 5xx e timeouts — nunca em [BadRequest]
  • POST protegido por consulta prévia com chave de negócio própria
  • Nenhuma varredura integral em ciclo — carga inicial só uma vez
  • Sincronização por SinceDateLastRecordModification com 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 (0 ou 1) do endpoint usado
  • Cursor (LastID, SinceIDSku) preferido a offset em volume grande
  • Endpoint de webhook idempotente, respondendo 2xx em 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