Documentação da API

Cote frete, gere etiqueta com declaração de conteúdo e acompanhe pedidos — pague por Pix via Pangeia Pay, sem precisar de saldo pré-carregado.

Autenticação

Toda chamada autenticada usa sua API key no header Authorization. Gere uma key no dashboard, aba "Área do desenvolvedor".

Authorization: Bearer pf_live_xxxxxxxxxxxxxxxxxxxxxxxx

A API key só é mostrada uma vez, no momento em que você gera. Se perder, revogue e gere outra.

Só para Pessoa Física

O BABYBLUE é feito exclusivamente pra Pessoa Física. A conta Pangeia ID por trás do seu login (ou da API key) precisa ter CPF cadastrado no campo taxid — conta de empresa (CNPJ) ou sem nenhum documento cadastrado não consegue autenticar em nenhum endpoint, nem pelo dashboard nem pela API.

HTTP 401 (toda chamada autenticada, se a conta estiver bloqueada)
{"error": "É necessário estar logado (ou usar API key)"}

Pra saber o motivo específico do bloqueio (útil se você estiver implementando o login do BABYBLUE dentro do seu próprio sistema), confira o retorno de /auth/me logo após o login — ele responde {"ok": false, "blocked_reason": "cnpj"} ou {"ok": false, "blocked_reason": "sem_taxid"} antes de qualquer chamada autenticada falhar.

Limites de taxa

Toda resposta traz os headers X-RateLimit-Limit, X-RateLimit-Remaining e X-RateLimit-Reset (epoch em segundos de quando a cota renova). Estourou o limite: 429.

A cota é contada por API key quando a chamada vem com Authorization: Bearer — não por IP. Isso importa se você roteia várias contas/lojas pelo mesmo backend (ex: marketplace): cada API key tem sua cota própria, elas não competem entre si por saírem do mesmo IP. Endpoints sem autenticação (cotação e CEP) contam por IP, já que não há key pra usar.

EndpointLimite
POST /auth/exchange-hash20/minuto
POST /api/frete600/minuto
GET /api/cep/<cep>1000/minuto
GET /api/embalagens1000/minuto
webhooks/* (pangeia-id, pangeia-pay)sem limite — autenticados por assinatura própria, não por IP/key
Todo o resto300/minuto (padrão)
HTTP 429
{"error": "..."}
X-RateLimit-Limit: 600
X-RateLimit-Remaining: 0
X-RateLimit-Reset: 1786288800

Precisa de mais que isso pro seu volume? Fala com a gente — os limites acima são o padrão, não um teto rígido por contrato.

Consulta de CEP

Consulta de endereço por CEP com cache persistente — a primeira consulta de um CEP busca no ViaCEP e fica salva no nosso banco pra sempre; toda consulta seguinte pro mesmo CEP não bate mais em nenhum serviço externo. Não exige autenticação.

GET/api/cep/<cep>
GET /api/cep/01415000

{"address": "Rua Bela Cintra", "district": "Consolação", "city": "São Paulo", "state_abbr": "SP"}

404 se o CEP não existir. Só aceita os 8 dígitos, com ou sem hífen.

Embalagens

Catálogo de embalagens da Loja BABYBLUE — a Loja vende só embalagens (caixas), então todo item aqui é uma delas. Pensado pra quem faz bin-packing do próprio lado (decide como distribuir uma lista de itens entre embalagens) e, com o resultado, chama a cotação aqui embaixo. Não exige autenticação.

GET/api/embalagens
[
  {
    "id": "...", "codigo": "CX-P", "nome": "Caixa P",
    "width": 20, "height": 12, "length": 30, "peso": 0.08, "peso_suportado": 5
  }
]

width/height/length são as dimensões externas da embalagem em cm (até 1 casa decimal), peso é a tara dela vazia em kg (até 3 casas decimais) — soma no peso total do volume junto com o que for colocado dentro — e peso_suportado é o quanto essa embalagem aguenta de conteúdo dentro, também em kg. codigo é o identificador estável dessa embalagem no catálogo (definido no admin), útil pra referenciar sem depender do id do Mongo.

Cotação

POST/api/frete

Cotação por CEP de origem e destino. O BABYBLUE trabalha exclusivamente com os Correios — a lista só traz serviços deles (SEDEX, PAC etc.), postados em qualquer agência da cidade de origem.

CampoTipo
from_postal_codestring
to_postal_codestring
width / height / lengthnumber (cm, até 1 casa decimal)
weightnumber (kg, até 3 casas decimais)
insurance_valuenumber — valor declarado do conteúdo (R$); opcional, padrão 0. Não muda o preço retornado nesta cotação (o seguro é considerado só na hora de gerar a etiqueta de verdade), mas informe mesmo assim se já souber o valor do conteúdo
quantitynumber — quantos volumes idênticos; opcional, padrão 1
[
  {
    "id": 2, "service": "SEDEX",
    "carrier": {"id": 1, "name": "Correios", "logo_url": "/midia/transportadora/correios.png"},
    "price": 26.28, "currency": "R$",
    "delivery_time": {"min": 3, "max": 5}
  }
]

A lista varia por rota. Nem todo serviço dos Correios atende toda combinação de origem/destino (o PAC, por exemplo, não cobre trechos muito curtos) — a gente já filtra isso antes de responder, então tudo que vier na lista é realmente utilizável.

Preço cotado não é garantido até o envio ser reservado de verdade (o que só acontece na geração, depois do pagamento confirmado — pode levar tempo num pedido via API, pago por Pix). Se o custo real vier diferente do cotado, o evento etiqueta_gerada avisa com os dois valores — ver Webhooks.

Limites de peso e dimensão

O BABYBLUE não valida isso antes de cotar — quem decide se um volume é aceito é o Correios na hora (fora da faixa, o serviço simplesmente não aparece na lista de resposta). Como referência, os limites de hoje:

ModalidadeMínimo (C×L×A)Máximo (C×L×A)Peso máximo
PAC / SEDEX11 × 6 × 0,4 cm100 × 100 × 100 cm (soma até 200 cm)30 kg
Mini Envios11 × 6 × 0,4 cm24 × 16 × 4 cm300 g

⚠️ Isso pode mudar a qualquer momento — são regras do Correios, não da API do BABYBLUE, e a gente não controla nem é avisado quando elas mudam. Não trave validação rígida no seu lado em cima desses números; use a resposta da cotação (o que não veio na lista, não está disponível) como fonte de verdade. Pra confirmar o valor atual, consulte sempre a página oficial dos Correios: correios.com.br/sistemas/precosprazos/Formato.cfm (checado em 09/08/2026).

Pedidos / Etiquetas

POST /api/pedidos cria um lote de pedidos pago por Pix via Pangeia Pay — sem carteira, sem saldo pré-carregado. Só funciona com api_key.

POST/api/pedidos

Cada item do lote é um pedido 100% independente: seu próprio remetente, destinatário, produtos e serviço — os Correios só geram uma etiqueta por pacote postado, então não dá (nem faz sentido) combinar vários num pedido só. Uma única cobrança Pix paga o lote inteiro; cada pedido resultante gera (ou falha) na sua própria etiqueta, independente dos outros.

CampoTipoObrigatório
pedidosarray de objetos — ver campos abaixosim, ao menos 1

Cada item de pedidos:

CampoTipoObrigatório
remetenteobjeto — ver formato abaixosim
destinatarioobjeto — mesmo formato de remetentesim
produtosarray de {name, quantity, unitary_value}sim
volume{width, height, length, weight} — width/height/length em cm (até 1 casa decimal), weight em kg (até 3 casas decimais)sim
service_idnumber — id retornado pela cotação (cote o CEP desse remetente pro CEP desse destinatário)sim
aceite_medidasboolean — precisa ser true, por pedidosim
referencia_externastring (até 100 caracteres) — identificador desse pedido específico no seu sistemanão

remetente e destinatario usam o mesmo formato:

CampoTipoObrigatório
namestringsim
documentstring (CPF ou CNPJ, só números)sim
emailstringsim
phonestringsim
addressstring (logradouro)sim
numberstringsim
complementstringnão
districtstring (bairro)sim
citystringsim
state_abbrstring (UF, 2 letras)sim
postal_codestring (CEP, com ou sem hífen)sim

Faltou algum campo obrigatório de qualquer um dos dois num pedido do lote? 400 com {"error": "Campos do remetente faltando: document, phone"} (ou destinatário), listando exatamente o que falta.

aceite_medidas confirma que quem está gerando a etiqueta conferiu peso e dimensões antes de enviar — sem true, o pedido é rejeitado com 400. Se o Correios pesar/medir diferente na postagem, o evento diferenca_frete avisa seu webhook_url com o código de rastreio do pacote, e a cobrança vira uma dívida própria — ver Diferenças de frete.

A etiqueta vale por 7 dias corridos a partir da geração — é o prazo que os Correios dão pra postar (a gente não tem como saber se/quando você imprimiu de fato, o prazo corre a partir de quando a etiqueta foi gerada aqui, não de quando foi impressa). Postando depois disso, ela não é mais aceita na agência.

POST /api/pedidos
{
  "pedidos": [
    {
      "remetente": {...}, "destinatario": {...},
      "produtos": [{"name": "Camiseta", "quantity": 2, "unitary_value": 39.9}],
      "volume": {"width": 20, "height": 10, "length": 30, "weight": 0.6},
      "service_id": 2, "aceite_medidas": true, "referencia_externa": "PEDIDO-1"
    },
    {
      "remetente": {...}, "destinatario": {...},
      "produtos": [{"name": "Boné", "quantity": 1, "unitary_value": 59.9}],
      "volume": {"width": 16, "height": 11, "length": 24, "weight": 0.3},
      "service_id": 1, "aceite_medidas": true, "referencia_externa": "PEDIDO-2"
    }
  ]
}

{
  "lote_id": "...", "status": "aguardando_pagamento", "valor": 84.29,
  "pagamento_url": "https://pay.pangeialabs.com/pagar/...",
  "pix_code": "00020126...6304XXXX",
  "pix_qrcode_base64": "iVBORw0KGgo...",
  "pedidos": [
    {"id": "...", "referencia_externa": "PEDIDO-1"},
    {"id": "...", "referencia_externa": "PEDIDO-2"}
  ]
}

valor é a soma do preço de todos os pedidos do lote (pode sair com centavos ajustados em relação à soma exata — é assim que o Pangeia Pay identifica qual Pix corresponde a qual cobrança). Pague, e o webhook etiqueta_gerada (ver Webhooks) chega um evento por pedido assim que cada etiqueta for gerada — ou consulte GET /api/pedidos/<id> a qualquer momento com o id de cada pedido retornado acima.

Se algum pedido do lote falhar na geração (ex: CEP inválido, transportadora fora do ar), isso não trava os outros — cada um gera (ou falha) independente. Um pedido com falha fica com status: "erro_geracao"; fale com a gente pra resolver, não tem estorno automático nesse caso (já que o pagamento é único pro lote inteiro, não por pedido).

GET/api/pedidos?limit=50

Lista os pedidos da sua conta (gerados pelo dashboard ou pela API), mais recentes primeiro. limit é opcional (padrão 50, máx. 200).

GET/api/pedidos/<id>

Detalhe de um pedido específico — mesmo formato pra pedido criado pelo dashboard ou por um lote via API. envio vem preenchido assim que gerado (tracking_code, label_url).

GET/api/pedidos/<id>/rastreio

Atualiza e retorna o status de rastreio da transportadora.

GET/api/pedidos/<id>/etiqueta

PDF/material de remessa (etiqueta + declaração de conteúdo).

Diferenças de frete (dívidas)

Os Correios podem medir peso/dimensão diferente do que foi informado na cotação, depois que o pacote já saiu — isso gera uma diferença de valor. Quando isso acontece num pedido criado com sua api_key, o evento diferenca_frete (ver Webhooks) avisa você. A API não usa carteira/saldo — nada é debitado de nenhum saldo compartilhado seu. Em vez disso, cada diferença vira uma dívida isolada, com sua própria cobrança Pix dedicada — o mesmo princípio de POST /api/pedidos: um pagamento, amarrado a uma coisa só, nunca um saldo genérico que qualquer chamada pudesse consumir.

GET/api/dividas/<id>

Consulta uma dívida específica — útil pra conferir se já foi paga sem depender só do webhook (que é best-effort).

{
  "id": "...", "pid": "...", "pedido_id": "...",
  "valor": 18.40, "motivo": "Peso divergente identificado pela transportadora",
  "status": "informada", "created_at": "..."
}

status: informada (ainda sem cobrança gerada) → aguardando_pagamento (depois de POST .../pagamento) → paga.

POST/api/dividas/<id>/pagamento

Gera uma cobrança Pix pra essa dívida específica — chame isso na hora que for de fato cobrar seu cliente, não antes. O código Pix do Pangeia Pay expira em 30 minutos; como o webhook diferenca_frete pode chegar bem antes de você estar pronto pra mostrar a cobrança pra alguém, o Pix não vem pronto nele — é gerado sob demanda aqui, com uma validade nova a cada chamada. Pode chamar de novo quantas vezes precisar (ex: o anterior expirou sem pagar) — cada chamada é uma cobrança nova.

POST /api/dividas/<id>/pagamento

{
  "id": "...", "valor": 18.40,
  "pagamento_url": "https://pay.pangeialabs.com/pagar/...",
  "pix_code": "00020126...6304XXXX",
  "pix_qrcode_base64": "iVBORw0KGgo..."
}

Chamar numa dívida já paga: 400 com {"error": "Essa dívida já foi paga"}. Confirmação de pagamento chega pelo mesmo mecanismo de sempre — consulte GET /api/dividas/<id> pra ver status: "paga", best-effort, sem webhook de confirmação separado.

Webhooks

Cada app cadastrado no dashboard tem sua própria webhook_url e webhook_secret. Eventos são enviados por POST pra essa URL, assinados com HMAC-SHA256.

diferenca_frete

Disparado quando a gente identifica (automaticamente, conferindo o preço pós-postagem, ou manualmente) uma diferença de peso/dimensão num pedido criado com sua API key. valor já sai com a mesma margem de qualquer outro preço que a API mostra, e tracking_code identifica exatamente qual pacote gerou a diferença.

{
  "event": "diferenca_frete",
  "timestamp": "2026-08-04T12:00:00Z",
  "data": {
    "divida_id": "...",
    "pedido_id": "...",
    "referencia_externa": "PEDIDO-1000007",
    "tracking_code": "AA123456789BR",
    "valor": 18.40,
    "motivo": "Peso divergente identificado pela transportadora"
  }
}

Isso não é cobrado automaticamente — a API não usa carteira/saldo pra pagar nada (ver Pedidos), então não existe um saldo compartilhado de onde descontar a diferença sem risco de misturar crédito de gente diferente. Esse evento só avisa que a diferença existe; pra cobrar de verdade, veja Diferenças de frete (dívidas) logo abaixo — o evento não vem com QR code/Pix pronto de propósito (o código expiraria antes de você conseguir mostrar pro seu cliente).

etiqueta_gerada

Disparado uma vez por pedido, assim que o envio é gerado — só pra pedidos criados via POST /api/pedidos (o fluxo do dashboard não usa webhook, já devolve tudo pronto na resposta). Num lote com vários pedidos, chega um evento separado por pedido, cada um assim que fica pronto (não espera os outros).

{
  "event": "etiqueta_gerada",
  "timestamp": "2026-08-09T12:00:00Z",
  "data": {
    "pedido_id": "...",
    "referencia_externa": "PEDIDO-1000007",
    "tracking_code": "AA123456789BR",
    "label_url": "/api/pedidos/.../etiqueta",
    "valor_cobrado": 26.28,
    "custo_real": 24.30,
    "diferenca_estimativa": -1.98
  }
}

Preço cotado não é garantido até o envio ser reservado de verdade — e isso só acontece na geração, depois do pagamento confirmado (pode levar minutos ou horas, dependendo de quando o Pix é pago). Por isso todo evento traz três valores pra você conciliar do seu lado: valor_cobrado (o que já foi cobrado na criação do pedido) e custo_real (o valor de fato confirmado na hora de gerar) — os dois já com a mesma margem aplicada, então dá pra comparar direto — e diferenca_estimativa (custo_real menos valor_cobrado — positivo significa que o custo real veio maior que o cobrado). A gente não corrige nada automaticamente nem cobra diferença do cliente por causa disso — só informa, pra você lançar do seu lado se precisar. Mesmos três campos ficam disponíveis em envio.custo_real/envio.diferenca_estimativa via GET /api/pedidos/<id>.

Mais eventos (postada, entregue, cancelada) ainda estão a caminho — hoje diferenca_frete e etiqueta_gerada estão ativos.

Verificando a assinatura

Todo POST vem com o header X-BabyBlue-Signature: sha256=<hex>, calculado sobre o corpo bruto da requisição (bytes exatos, antes de qualquer parse) usando seu webhook_secret como chave:

assinatura_esperada = hmac_sha256(chave=webhook_secret, mensagem=corpo_bruto_da_requisicao)
# compare em tempo constante (hmac.compare_digest em Python, timingSafeEqual em Node, etc.)

Responda 2xx rápido pra confirmar o recebimento — se a sua URL não responder, a gente não fica tentando de novo indefinidamente, então trate isso como best-effort. Desconfiou que perdeu algum evento? Fala com a gente.

Erros

HTTPSignificado
400Dados inválidos ou faltando
401API key ausente, inválida ou revogada
404Recurso não encontrado
429Limite de taxa excedido (ver Limites de taxa) — respeite X-RateLimit-Reset antes de tentar de novo
502Falha ao falar com a transportadora ou o meio de pagamento

Toda resposta de erro segue o formato {"error": "mensagem legível"}.