| Código | Status | Significado | Ação |
|---|---|---|---|
202 | Accepted | Requisição enfileirada para processamento | Consulte GET com aggregationId para status real |
400 | Bad Request | Erro de validação no payload | Revise campos obrigatórios e formato |
401 | Unauthorized | Token JWT inválido ou expirado | Renovar token de acesso |
404 | Not Found | Loja (merchantId) ou agregação não existe | Verificar os IDs |
412 | Precondition Failed | Validação falhou (ex: formato de data) | Verificar campos e formatos |
429 | Too Many Requests | Limite de requisições excedido | Implementar backoff exponencial |
500 | Internal Server Error | Erro no backend | Contatar suporte com aggregationId |
ERROR, o campo error contém um código específico:| Código | Significado | Solução |
|---|---|---|
DATE_INVALID | Data em formato inválido ou finalDate < initialDate | Use ISO 8601 (YYYY-MM-DD) e garanta initialDate ≤ finalDate |
PROMOTION_TYPE_INVALID | Tipo não reconhecido | Usar PERCENTAGE, LXPY ou PERCENTAGE_PER_X_UNITS |
DISCOUNT_INVALID | Desconto > 70% ou campos faltantes | Verificar regras de validação por tipo |
ITEM_NOT_FOUND | EAN não existe ou produto inativo | Validar EAN e confirmar que produto está ativo |
DUPLICATE_ITEM | Mesmo EAN e período já existe | Verificar promoções existentes com GET ou usar reset=true |
INVALID_CHANNEL | Canal não válido | Usar canais válidos (ex: IFOOD-APP) |
INTERNAL_ERROR | Falha no backend | Aguardar e reenviar. Se persistir, contatar suporte |
curl -X GET "https://merchant-api.ifood.com.br/promotion/v1.0/merchants/{merchantId}/promotions/{aggregationId}/items" \
-H "Authorization: Bearer {TOKEN}"status:PROCESSING → Ainda sendo validada. Aguarde 2-3 segundos.ERROR → Verifique campo error para código específico.ACTIVE → Validada, mas pode não estar visível em pedidos.PROCESSING: Aguarde alguns segundos e consulte novamentesleep 3
curl -X GET "https://merchant-api.ifood.com.br/promotion/v1.0/merchants/{merchantId}/promotions/{aggregationId}/items" \
-H "Authorization: Bearer {TOKEN}"ERROR: Consulte o código no campo error{
"promotions": [
{
"status": "ERROR",
"error": "DATE_INVALID"
}
]
}ACTIVE: Prossiga para "Desconto não aparece em pedidos" abaixo.PROCESSING ou ERROR indefinidamente.Causas prováveis:POSTPROCESSINGPROCESSING, investigarsleep 10
curl -X GET "https://merchant-api.ifood.com.br/promotion/v1.0/merchants/{merchantId}/promotions/{aggregationId}/items" \
-H "Authorization: Bearer {TOKEN}"ERROR, verifique o código:curl -X GET "https://merchant-api.ifood.com.br/promotion/v1.0/merchants/{merchantId}/promotions/{aggregationId}/items?status=ERROR" \
-H "Authorization: Bearer {TOKEN}"curl -X POST "https://merchant-api.ifood.com.br/promotion/v1.0/merchants/{merchantId}/promotions" \
-H "Authorization: Bearer {TOKEN}" \
-H "Content-Type: application/json" \
-d '{
"aggregationTag": "promo-corrigido-001",
"promotions": [ ... ]
}'aggregationTag para cada tentativa (não reutilize anterior).ACTIVE, mas o cliente não vê o desconto aplicado ao fazer uma compra.Causas prováveis:curl -X GET "https://merchant-api.ifood.com.br/promotion/v1.0/merchants/{merchantId}/promotions/{aggregationId}/items?ean=7891234567890&status=ACTIVE" \
-H "Authorization: Bearer {TOKEN}"# Consultar detalhes do pedido
curl -X GET "https://merchant-api.ifood.com.br/order/v1.0/merchants/{merchantId}/orders/{orderId}/virtual-bag" \
-H "Authorization: Bearer {TOKEN}"{
"bag": {
"benefits": {
"benefits": [
{
"target": "ITEM",
"targetId": "7891234567890",
"sponsorships": [
{
"liability": "PARTNER",
"amount": {
"value": 5.00
}
}
]
}
]
}
}
}reset=true429 Too Many Requests.Causa: Limite de requisições por unidade de tempo foi excedido.Solução:Implemente backoff exponencial para retries:#!/bin/bash
# Exemplo de retry com backoff exponencial
function call_api_with_retry() {
local url="$1"
local data="$2"
local attempt=1
local max_attempts=5
while [ $attempt -le $max_attempts ]; do
# Fazer a requisição
response=$(curl -s -w "\n%{http_code}" -X POST "$url" \
-H "Authorization: Bearer {TOKEN}" \
-H "Content-Type: application/json" \
-d "$data")
# Extrair código HTTP
http_code=$(echo "$response" | tail -n1)
body=$(echo "$response" | head -n-1)
if [ "$http_code" != "429" ]; then
echo "$body"
return 0
fi
# Calcular tempo de espera: 2^attempt segundos
wait_time=$((2 ** attempt))
echo "Rate limited. Aguardando ${wait_time}s antes da tentativa $((attempt + 1))..." >&2
sleep "$wait_time"
((attempt++))
done
echo "Falha após $max_attempts tentativas" >&2
return 1
}401 Unauthorized.Causas:curl -X POST "https://oauth.ifood.com.br/oauth/authorize" \
-H "Content-Type: application/x-www-form-urlencoded" \
-d "grant_type=client_credentials&client_id={CLIENT_ID}&client_secret={CLIENT_SECRET}"curl -X GET "https://merchant-api.ifood.com.br/promotion/v1.0/merchants/{merchantId}/promotions/{aggregationId}/items" \
-H "Authorization: Bearer {NEW_TOKEN}"function validatePromotion(promo) {
// Validar formato de data
const dateRegex = /^\d{4}-\d{2}-\d{2}$/;
if (!dateRegex.test(promo.initialDate)) {
throw new Error('Data inválida: use YYYY-MM-DD');
}
// Validar datas lógicas
if (new Date(promo.initialDate) > new Date(promo.finalDate)) {
throw new Error('initialDate não pode ser posterior a finalDate');
}
// Validar desconto
if (promo.discountValue > 70) {
throw new Error('Desconto não pode exceder 70%');
}
// Validar tipo de promoção
const validTypes = ['PERCENTAGE', 'LXPY', 'PERCENTAGE_PER_X_UNITS'];
if (!validTypes.includes(promo.promotionType)) {
throw new Error(`Tipo inválido. Use: ${validTypes.join(', ')}`);
}
return true;
}function logPromotion(action, data, response) {
console.log(JSON.stringify({
timestamp: new Date().toISOString(),
action: action,
aggregationId: data.aggregationTag || response.aggregationId,
merchantId: data.merchantId,
itemCount: data.promotions?.[0]?.items?.length || 0,
httpCode: response.status,
errorCode: response.error,
}, null, 2));
}const retryConfig = {
maxAttempts: 3,
initialDelayMs: 1000,
backoffMultiplier: 2,
retryableStatusCodes: [429, 500, 502, 503, 504],
};
async function makeRequestWithRetry(fn, config = retryConfig) {
let attempt = 1;
let delayMs = config.initialDelayMs;
while (attempt <= config.maxAttempts) {
try {
return await fn();
} catch (error) {
if (!config.retryableStatusCodes.includes(error.statusCode)) {
throw error; // Não é retentável
}
if (attempt === config.maxAttempts) {
throw error; // Última tentativa
}
await sleep(delayMs);
delayMs *= config.backoffMultiplier;
attempt++;
}
}
}PROCESSING por muito tempo