Boas práticas
Limites de requisição, tabela de erros, datas em UTC, backoff, guarda do segredo e versão do padrão.
Esta página reúne o que uma integração precisa fazer certo para operar sem intervenção: respeitar limites, tratar cada erro do jeito esperado, ler datas sem erro de fuso, repetir só o que pode ser repetido e proteger a credencial.
Limites de requisição
| Limite | Valor | Resposta ao exceder |
|---|---|---|
| Chamadas autenticadas, por credencial | 600 por minuto | 429 com Retry-After: 60 |
POST /oauth/token, por IP | 60 por minuto | 429 com Retry-After: 60 |
Falhas de autenticação em POST /oauth/token, por credencial | 10 por minuto | 429 com Retry-After: 60 |
Ao exceder, a API responde:
HTTP/1.1 429 Too Many Requests
Retry-After: 60
Content-Type: application/json
{ "error": "rate_limit_exceeded", "message": "Limite de requisições excedido." }Como ficar longe do limite:
- Faça polling em um intervalo fixo. Uma consulta a cada 5 segundos são 12 chamadas por minuto, mais a busca de cada pedido e a confirmação dos eventos. Uma loja movimentada fica bem abaixo de 600.
- Reutilize o token. Ele vale por 3600 segundos. Peça um novo só quando faltar pouco para expirar ou quando receber
401. Pedir token a cada chamada esgota o limite por IP. - Não repita uma chamada que falhou com
4xx(exceto429). O resultado será o mesmo. - Ao receber
429, espere o valor deRetry-Afterinteiro antes da próxima chamada, para qualquer rota da mesma credencial.
Tabela de erros
Todos os erros de negócio vêm em JSON com error (código estável, para o seu código) e message (texto em pt-BR, para o seu log). Duas exceções: o endpoint de token segue o RFC 6749 e usa error_description no lugar de message (o 401 vem só com error), e os 404 de rota e os 400 de validação de formato usam o formato ProblemDetails, descrito na seção seguinte.
| Status | error | Quando acontece | O que fazer |
|---|---|---|---|
400 | invalid_request | Corpo do POST /oauth/token sem client_id, client_secret ou malformado. | Corrija a requisição. Não repita. |
400 | unsupported_grant_type | grant_type diferente de client_credentials. | Envie grant_type=client_credentials. |
400 | ValidationProblemDetails | Corpo com campo inválido em uma rota de negócio (ex.: acknowledgment sem id). | Leia errors e corrija o campo. |
401 | invalid_client | Credencial inexistente, segredo errado, credencial pausada ou revogada, no endpoint de token. | Confira client_id e client_secret. Se a credencial foi pausada, peça ao lojista para retomar. Não repita em loop: a partir de 10 falhas por minuto o segredo errado passa a receber 429 por 60 s. |
401 | invalid_token | Credencial pausada ou revogada, em qualquer chamada, mesmo com token ainda dentro da validade. | Pare o polling e avise o operador. Só volta a funcionar quando o lojista retomar a credencial. |
401 | corpo vazio, header WWW-Authenticate | Sem Authorization, token expirado ou assinatura inválida. | Peça um token novo e repita a chamada uma vez. |
403 | insufficient_scope | O token não tem o escopo exigido pela rota. | Crie uma credencial com o escopo certo. Escopos não são editáveis. |
404 | order_not_found | orderId desconhecido ou de outra loja. | Não repita. Se o id veio de um evento, guarde o traceId e fale com o suporte. |
404 | ProblemDetails | Rota inexistente, merchantId diferente do da credencial ou orderId que não é um GUID. | Confira a URL e o merchantId. |
409 | idempotency_key_reuse | A mesma Idempotency-Key foi usada com uma requisição diferente. | Gere uma chave nova para a nova requisição. |
415 | Unsupported Media Type | Content-Type não suportado no POST /oauth/token. | Use application/x-www-form-urlencoded ou application/json. |
422 | invalid_transition | A ação não vale para o estado atual do pedido (ex.: cancelar um pedido DONE). | Leia message, busque o pedido para ver o estado e ajuste o fluxo. Não repita. |
429 | rate_limit_exceeded | Limite de requisições excedido. | Espere Retry-After segundos e repita. |
500 | internal_error | Falha interna. | Repita com backoff. Se persistir, envie o traceId ao suporte. |
Respostas que não são erro
202 com status: "already_applied" significa que o pedido já estava no estado pedido. Trate como sucesso. Um array vazio em GET /v1/events:polling significa que não há eventos pendentes; a API nunca responde 204.
Formatos de erro
Erro de negócio. A maioria das respostas de erro:
{ "error": "invalid_transition", "message": "O pedido já foi concluído e não pode ser cancelado." }Erro interno. Igual ao anterior, com traceId para o suporte:
{
"error": "internal_error",
"message": "Erro interno. Informe o traceId ao suporte.",
"traceId": "00-9a3f1c7e2b5d4a6f8e1c0b9d7a5f3e21-4b8c2d1e6f0a9b37-00"
}ProblemDetails. Os 404 automáticos (rota inexistente, merchantId de outra loja, orderId que não é GUID):
{
"type": "https://tools.ietf.org/html/rfc9110#section-15.5.5",
"title": "Not Found",
"status": 404,
"traceId": "00-9a3f1c7e2b5d4a6f8e1c0b9d7a5f3e21-4b8c2d1e6f0a9b37-00"
}ValidationProblemDetails. Os 400 de validação de corpo, com um mapa errors por campo:
{
"type": "https://tools.ietf.org/html/rfc9110#section-15.5.1",
"title": "One or more validation errors occurred.",
"status": 400,
"errors": {
"[0].id": ["The id field is required."]
},
"traceId": "00-9a3f1c7e2b5d4a6f8e1c0b9d7a5f3e21-4b8c2d1e6f0a9b37-00"
}Para decidir o que fazer, use o status HTTP primeiro e o campo error depois. Não dependa do texto de message: ele pode mudar.
Datas: sempre UTC com Z
Toda data da API é ISO 8601 em UTC, com o sufixo Z: 2026-09-19T18:42:07Z. Isso vale para createdAt de eventos e pedidos, preparationStartDateTime, scheduledDateTimeStart, deliveryDateTime, lastUpdate e todas as outras.
Três regras:
- Leia como UTC. Bibliotecas que ignoram o
Zinterpretam a data no fuso local e produzem um erro de horas. Converta para o fuso da loja só na hora de exibir. - Compare em UTC. Ordenação e cálculo de SLA (tempo até confirmar, tempo até despachar) devem ser feitos sobre o instante, não sobre a hora local.
- Não envie datas. Nenhuma rota atual recebe data no corpo; o servidor registra o instante das ações.
// `Date` respeita o sufixo Z. Guarde o instante, formate na exibição.
const createdAt = new Date(event.createdAt);
const local = new Intl.DateTimeFormat('pt-BR', {
timeZone: 'America/Sao_Paulo',
dateStyle: 'short',
timeStyle: 'medium',
}).format(createdAt);Repetição com backoff
Repita só falhas transitórias: 429, 500, timeout e erro de rede. Use backoff exponencial com jitter, respeite Retry-After quando ele existir e limite o número de tentativas. Nunca repita 400, 401 (exceto uma vez, após renovar o token), 403, 404, 409 ou 422.
async function withRetry<T>(fn: () => Promise<Response>, attempts = 5): Promise<Response> {
for (let attempt = 0; ; attempt++) {
let response: Response | undefined;
try {
response = await fn();
} catch (error) {
// Erro de rede ou timeout: cai no backoff abaixo.
if (attempt + 1 >= attempts) throw error;
}
if (response && response.status !== 429 && response.status < 500) return response;
if (response && attempt + 1 >= attempts) return response;
const retryAfter = Number(response?.headers.get('Retry-After'));
const base = Number.isFinite(retryAfter) && retryAfter > 0 ? retryAfter * 1000 : 500 * 2 ** attempt;
const jitter = Math.random() * 250;
await new Promise((resolve) => setTimeout(resolve, Math.min(base + jitter, 60_000)));
}
}Duas garantias da API tornam a repetição segura:
- Polling é at-least-once. Se a sua chamada de
acknowledgmentfalhar, o evento volta na próxima consulta. Repetir nunca perde evento; por isso a deduplicação poreventIdé obrigatória do seu lado. - Ações aceitam
Idempotency-Key. Ao repetir umconfirmcom a mesma chave, a API devolve a resposta gravada em vez de aplicar a ação de novo. Veja Ações e idempotência.
Token: renove antes de expirar
O token vale 3600 segundos e não existe refresh token. Guarde expires_in junto com o instante em que o token chegou e peça outro quando faltar cerca de 60 segundos. Se uma chamada devolver 401 sem corpo, renove e repita uma única vez. Se devolver 401 com invalid_token, a credencial foi pausada ou revogada: renovar não resolve; pare e avise o operador.
Uma credencial vale para uma loja. Não compartilhe token entre lojas nem entre processos que não confiam um no outro.
Segurança do segredo
- O
client_secreté mostrado uma única vez, ao criar a credencial. Guarde em um cofre de segredos ou variável de ambiente do servidor. Nunca em código-fonte, repositório, planilha ou mensagem. - A API só aceita chamadas de navegador vindas deste portal (CORS liberado apenas para
https://developer.meupedido.io). Integrações são servidor a servidor. Um segredo embutido em app móvel ou página web está exposto e não funcionaria de qualquer forma. - Não registre
client_secret,Authorizationnem o segredo do webhook em logs. Registreclient_id,traceId,eventIdeorderId: são os campos que o suporte pede. - Se o segredo vazou, o lojista revoga a credencial no painel (permanente) e cria outra. O painel não oferece troca do segredo de uma credencial existente: o caminho é revogar e criar de novo.
- O segredo do webhook rotaciona ao salvar a URL de novo no painel. Prepare o seu endpoint para aceitar o segredo novo antes de rotacionar.
- Verifique a assinatura de todo webhook com comparação em tempo constante e rejeite
X-MeuPedido-Timestampcom mais de 5 minutos de diferença do seu relógio. Veja Webhooks.
Versão do padrão e evolução
A API implementa o Open Delivery 1.4.0 (Abrasel), módulos Order e Merchant, com o MeuPedido no papel de aplicação de pedidos. Rotas, campos e valores seguem o padrão; o que é acréscimo do MeuPedido está marcado como Extensão MeuPedido na documentação e listado em Compatibilidade.
Para não quebrar quando a API evoluir:
- Ignore campos desconhecidos. Novos campos são adicionados sem mudar a versão da rota.
- Ignore
eventTypedesconhecido, registrando em log. Novos tipos de evento podem surgir como extensão. Confirme o evento mesmo assim, ou ele volta em toda consulta. - Não dependa da ordem de campos nem do texto de
message. - Acompanhe o Changelog. Toda mudança de comportamento é publicada lá.
Próximos passos
- Compatibilidade: o que está implementado do padrão 1.4.0 e o que é extensão.
- Checklist de entrada em produção: confira tudo isto antes de ligar a integração em uma loja real.
- Suporte: o que enviar quando algo não bate.