API do Mercos: o que testamos no sandbox

Vitor Ayres (abre em nova aba)

Desenho de plantas gerado a partir do primeiro commit do repositório mercos-sdk.
Plantas geradas pelo primeiro commit de mercos-sdk (6a33360).

O Mercos tem uma API de integração para pedidos, clientes, produtos, estoque e tabelas de preço. A documentação é pública. Antes de liberar o acesso à produção, o Mercos exige na homologação que a integração trate o limite de requisições e a paginação.

Este texto registra o que encontramos ao integrar com ela. A maior parte foi medida no sandbox; onde não foi, o texto diz. O sandbox pode se comportar diferente da produção, e a API pode mudar depois disso.

Resumo

  • A API não funciona no navegador: recusa o preflight de CORS, e os tokens dão acesso total à conta.
  • O limite de requisições vale para todas as rotas juntas. No sandbox, uma requisição a cada 5,5 segundos evitou o erro 429.
  • A paginação é por data de alteração, com resolução de um segundo, o que exige cuidado no corte das páginas.
  • As datas vêm no horário de Brasília.
  • A API não cria orçamento. O pedido nasce com o status Gerado.
  • O Mercos soma o IPI do cadastro a cada item do pedido, mesmo que você não mande.
  • Em produção, não dá para ler um pedido pelo ID, segundo a documentação do Mercos.

Só do servidor

A API recusa o preflight de CORS, então uma chamada feita pelo navegador falha. Mesmo que funcionasse, não seria uma boa ideia: os dois tokens, o da aplicação e o da empresa, dão acesso total à conta.

As chamadas precisam sair do seu servidor, que expõe ao navegador só aquilo de que a sua aplicação precisa.

Limite de requisições

O limite é global: vale para todas as rotas somadas, e não por rota. Ao passar dele, a API responde 429 e informa no campo tempo_ate_permitir_novamente quanto esperar.

A consequência prática é que disparar várias requisições em paralelo não adianta. Elas disputam o mesmo limite.

No sandbox, medimos:

Rodada Respostas 429 Tempo total
Seis leituras seguidas, sem intervalo 5 33 s
Seis leituras com 5,5 s entre elas 0 28 s

A rodada espaçada terminou antes e precisou de metade das requisições, porque não precisou repetir nada. O limite de uma conta de produção pode ser outro, então vale medir na sua.

Outro cuidado: uma escrita que não teve resposta não deve ser reenviada às cegas. O pedido pode ter sido criado mesmo sem a resposta chegar.

Paginação por data de alteração

As listagens são incrementais. O cursor é o parâmetro alterado_apos, e o cabeçalho MEUSPEDIDOS_LIMITOU_REGISTROS com valor 1 avisa que há mais páginas.

O problema é a resolução. O campo ultima_alteracao tem precisão de um segundo, e o corte de uma página pode cair no meio de um segundo. Se a próxima página começar exatamente depois do último registro recebido, os registros restantes daquele segundo podem ficar de fora.

O que fizemos:

  • Usar como próximo cursor a penúltima ultima_alteracao distinta da página, relendo o último segundo inteiro. Se a página termina em 10:00:01, 10:00:02 e 10:00:02, o próximo cursor é 10:00:01, e os registros de 10:00:02 vêm de novo.
  • Descartar os registros que voltam repetidos por causa desse recuo.
  • Tratar como erro o caso em que o cabeçalho avisa que há mais páginas e a página inteira tem a mesma ultima_alteracao, porque aí não há para onde recuar. Em pedidos, pedir mais registros por página costuma resolver.

Para sincronizar de forma incremental entre uma execução e outra, guarde a maior ultima_alteracao recebida e use esse valor na próxima, relendo o último segundo e descartando os repetidos.

Datas no horário de Brasília

O Mercos grava ultima_alteracao no horário de Brasília. Uma alteração feita às 00:12 UTC voltou marcada como 21:12 do dia anterior. Quem trabalha em UTC precisa converter antes de usar a data como cursor.

O formato também tem uma pegadinha. A documentação escreve alterado_apos como 2024-04-10T15:45:00. A maioria das rotas aceita, mas /v1/divisoes responde 422 e exige um espaço no lugar do T: 2024-04-10 15:45:00. Todas as rotas do sandbox aceitaram o espaço, então é o formato mais seguro.

Pedidos

A maior parte das surpresas está aqui.

  • Não existe orçamento pela API. O pedido criado pela API nasce com o status Gerado ("2"). As rotas /v2/pedidos e /v1/pedidos respondem 422 a um campo status, na criação e na alteração. Orçamento, o status "1", só sai pelas telas do Mercos.
  • A condição de pagamento é obrigatória, como condicao_pagamento_id ou como texto livre em condicao_pagamento. Sem nenhuma das duas, a resposta é 422, embora a documentação não marque nenhuma como obrigatória.
  • O ID do pedido criado vem no cabeçalho MeusPedidosID.
  • Em produção, não dá para ler um pedido pelo ID. Este ponto não vem dos nossos testes, e sim da página da rota: a leitura por ID existe só no sandbox, e depois da homologação ela não é permitida em produção, por desempenho. A documentação não diz qual erro a API devolve, e nós não medimos. A saída é procurar o pedido na listagem, filtrando por data de alteração. Quem precisar mesmo da leitura por ID pode pedir a liberação ao suporte do Mercos, com uma justificativa técnica.
  • A alteração é parcial. Um item com id altera aquele item. Um item sem id é incluído, mesmo que o produto já esteja no pedido. Um item com excluido: true é excluído, mas continua vindo na leitura, marcado como excluído. Os itens que o corpo não cita ficam como estão.

O IPI que você não mandou

O Mercos soma ao pedido o IPI do cadastro do produto, mesmo quando o item não manda ipi. Um exemplo medido no sandbox:

Item Enviado Subtotal no Mercos Conta que fecha
IPI de 5%, percentual 3 × 400, desconto de 10% 1134 3 × 360 × 1,05
IPI de R$ 12,50, valor fixo 4 × 200, desconto de 10% 770 4 × 180 + 4 × 12,50
Sem IPI 2 × 150 300 2 × 150

Duas conclusões da tabela:

  • O IPI percentual incide depois do desconto.
  • O IPI em valor fixo é por unidade, e o desconto não o reduz.

Além disso, o arredondamento para centavos acontece no subtotal de cada item.

Uma tela que soma só preço, quantidade e desconto vai mostrar um total menor que o real. Depois de criar o pedido, consulte-o de novo para ter o valor que o Mercos gravou.

Já a substituição tributária (st) do cadastro não entrou no pedido: os itens voltaram com st: 0. Não sabemos o motivo.

Estoque

Aqui medimos pouco. A conta de sandbox está com o controle de estoque desligado, e o Mercos recusa qualquer ajuste nessa condição com 422 e uma mensagem que diz isso. Foi o único comportamento de estoque que vimos acontecer.

As regras abaixo vêm da documentação, e o SDK as trata, mas não as confirmamos no sandbox:

  • O ajuste define o saldo, não soma nem subtrai. Você envia o saldo novo.
  • Cada requisição aceita no máximo 300 ajustes. Se um deles der erro, o lote inteiro é cancelado e nenhum saldo muda.
  • O ajuste vale para produtos simples e para os filhos de uma grade. No produto agregador, o pai da grade, a documentação diz que a API devolve erro.

Outras coisas que vale saber

  • Valores ausentes vêm como 0 ou "", não como null: tabela_preco_id: 0, transportadora_id: 0, observacoes: "". Um 0 num campo de ID quer dizer “sem valor”.
  • Os erros vêm em quatro formatos diferentes, conforme a rota.
  • A data de um campo extra vai como yyyy-mm-dd. A documentação mostra yyyy-dd-mm, que é erro de digitação: a API recusa e informa o formato certo.
  • Não há rota para os dados da própria empresa, como logotipo ou CNPJ.
  • Quantidade fracionada, como 1,5, é aceita.

Como verificamos

Os testes rodaram contra o sandbox do Mercos. Parte veio de uma suíte automatizada, que cria um pedido e o cancela no fim. O resto veio de uma aplicação nossa que usa o SDK no dia a dia, também apontada para o sandbox.

A documentação do Mercos traz trechos de OpenAPI embutidos em cada página. Juntamos esses trechos em um único arquivo, com 169 operações, e registramos as correções num arquivo à parte, cada uma com o motivo. Dois defeitos se repetem nas páginas: um objeto declarado quando o exemplo mostra uma lista, ou o contrário, e a mesma rota documentada com corpos diferentes em páginas diferentes.

Um SDK que já trata isso

Publicamos o mercos-sdk, um SDK em TypeScript com licença MIT. Ele usa o fetch nativo, sem dependências, e roda em Node 22 ou mais recente, Deno, Bun e Cloudflare Workers.

Ele envia uma requisição por vez e espera o tempo indicado na resposta 429, faz a paginação com o recuo descrito acima, nunca reenvia uma escrita, converte um Date para o horário de Brasília e junta os quatro formatos de erro em um só. Também se recusa a iniciar no navegador, para os tokens não vazarem. Por padrão ele não espaça as requisições; o intervalo de 5,5 segundos se configura com a opção minIntervalMs.

npm install mercos-sdk
import { createMercos } from "mercos-sdk";

const mercos = createMercos({
  applicationToken: process.env.MERCOS_APPLICATION_TOKEN!,
  companyToken: process.env.MERCOS_COMPANY_TOKEN!,
  environment: "sandbox",
});

for await (const cliente of mercos.clientes.list({ changedAfter: "2024-01-01 00:00:00" })) {
  console.log(cliente.id, cliente.razao_social);
}

O projeto não tem vínculo com a Mercos. Como o primeiro app que o usou mudou o desenho do SDK está no post sobre o SDK e o seu primeiro app, e o que fizemos para criar orçamento, que a API não faz, está no post sobre o caminho híbrido. Fizemos o mesmo tipo de levantamento com a API da ANVISA.

Se a sua empresa precisa ligar o Mercos a um ERP, a uma planilha ou a outro sistema, esse é o trabalho que fazemos em integrações.

Perguntas

Dá para chamar a API do Mercos direto do navegador?
Não. A API recusa o preflight de CORS, e os dois tokens dão acesso total à conta. As chamadas precisam sair de um servidor.
Qual é o limite de requisições da API do Mercos?
O limite vale para todas as rotas juntas. No sandbox, ele aceitava mais ou menos uma requisição a cada 5,5 segundos. O limite de uma conta de produção pode ser outro.
Dá para criar um orçamento pela API do Mercos?
Pelos nossos testes, não. O pedido criado pela API nasce com o status Gerado, e as rotas de pedido recusam o campo status com erro 422. Orçamento só sai pelas telas do Mercos.
Por que o total do pedido no Mercos é maior que a soma dos itens?
Porque o Mercos soma o IPI do cadastro do produto a cada item, mesmo quando o item não manda IPI. Consulte o pedido de novo depois de criar.
Como sincronizar só o que mudou?
Guarde a maior ultima_alteracao recebida e passe esse valor em alterado_apos na próxima consulta, relendo o último segundo e descartando os repetidos. A data vem no horário de Brasília.

Como podemos ajudar?

Oi (abre em nova aba)