PushMesh
Entrar Solicitar acesso
Abrir navegação das seções

Índice

Autenticação e erros

O essencial antes de escrever a primeira linha — o formato da chave, a rotação sem queda, o envelope de erro, a tabela de códigos, os limites de borda e por que a API não confirma o que existe.

Autenticação e erros

Esta é a página para ler antes de escrever a primeira linha de integração. Ela responde três perguntas: como você se identifica, o que a API devolve quando algo dá errado, e onde ficam as bordas.

A API pública vive em https://api.pushmesh.io e tem três formas de identificação. Confundi-las é o erro número um de quem começa:

Quem chamaO que enviaRotas
O seu servidorAuthorization: Basic pm_live_…envio, consulta, base de aparelhos, In-App, confirmações
O aparelho (via SDK ou cliente próprio)nada — o app_id viaja no corporegistro de aparelho, recibos, pacote In-App, parâmetros do Firebase
Quem opera a plataformaAuthorization: Bearer <chave de plataforma>criação de aplicativo e rotação por linha de comando

Existe ainda uma quarta credencial que não é da API: a sessão do painel em https://app.pushmesh.io. Ela autentica pessoas, dura 7 dias e nunca substitui a chave do aplicativo — que jamais é enviada ao navegador.


1. A chave do aplicativo

Formato

pm_live_a1b2c3d4_KE7pQ…43caracteres
   │        │           └─ segredo: 43 caracteres alfanuméricos
   │        └───────────── identificador da chave: 8 caracteres [a-z0-9]
   └────────────────────── prefixo: pm_live_ ou pm_test_

A validação é literal. Qualquer desvio de formato nem chega a consultar o banco: sai 401 na porta.

Onde ela vai

Authorization: Basic pm_live_a1b2c3d4_KE7pQ…

O interpretador do cabeçalho é deliberadamente tolerante, para funcionar com qualquer cliente HTTP. Estas três formas são equivalentes:

# 1. a chave crua depois de Basic (o jeito mais comum)
curl -H "Authorization: Basic $PUSHMESH_KEY" https://api.pushmesh.io/api/v1/confirmacoes

# 2. Bearer também é aceito (maiúsculas/minúsculas não importam)
curl -H "Authorization: Bearer $PUSHMESH_KEY" https://api.pushmesh.io/api/v1/confirmacoes

# 3. base64 de usuario:senha — o que o -u do curl e as bibliotecas clássicas produzem
curl -u "pushmesh:$PUSHMESH_KEY" https://api.pushmesh.io/api/v1/confirmacoes

Na terceira forma o usuário antes dos dois-pontos é ignorado; o que vale é a senha.

O que é guardado

Apenas um resumo criptográfico da chave. A chave em si não fica no banco, nem no log, nem no cache — não existe rota, tela ou pedido de suporte que a mostre de novo. A comparação é feita em tempo constante, de propósito.

pm_test_ é rótulo, não ambiente

Uma chave pode nascer com o prefixo pm_test_, e ele serve para você separar ambientes na sua configuração. Sendo honesto com você: o prefixo não muda nada no servidor — a resolução e as permissões são idênticas às da pm_live_. Não existe um ambiente de sandbox paralelo.

O ensaio de verdade tem outros dois caminhos, e os dois são reais:

  • o cabeçalho X-Dry-Run: true no envio, que resolve o público e não entrega nem grava;
  • tokens de aparelho começando com test:, que registram normalmente e ficam fora de qualquer disparo.

O app_id tem que ser o da chave

Quase toda rota autenticada exige o app_id (na query ou no corpo) além do cabeçalho. Se ele não for o aplicativo daquela chave, a resposta é 401 — não 404, e não 403. O motivo está na seção 7.


2. Rotacionar sem derrubar a integração

Rotacionar emite uma chave nova e mantém a anterior válida por 24 horas. É uma janela de convivência: nenhuma chamada em voo quebra, e você não precisa de uma janela de manutenção.

A resposta da rotação traz a chave nova (uma única vez) e o momento em que a anterior expira:

{
  "id": "00000000-0000-0000-0000-000000000000",
  "api_key": "pm_live_e5f6g7h8_novo_segredo_de_43_caracteres",
  "kid": "e5f6g7h8",
  "anterior_expira_em": "2026-08-28T14:05:00Z"
}

Sequência recomendada:

  1. rotacione (no painel ou pela rota de plataforma);
  2. guarde a chave nova no seu cofre de segredos;
  3. reinicie/atualize os serviços que usam a chave, com folga dentro das 24 h;
  4. confirme que nada mais usa a antiga antes do prazo.

Duas honestidades sobre o prazo:

  • A chave anterior continua funcionando durante a janela inteira. Rotação é troca sem queda, não corte imediato de acesso: se a sua chave vazou e você precisa invalidá-la agora, rotacionar não basta — fale com a plataforma.
  • A resolução de credencial é cacheada por 60 segundos. Na prática, uma chave já vencida ainda pode passar por até cerca de um minuto além do prazo, em instâncias que já a tinham em memória. É o pior caso, e vale a pena conhecê-lo antes de cronometrar um corte no relógio.

Depois da janela, a chave antiga responde:

{
  "errors": ["não autorizado"],
  "explain": {
    "causa": "chave inexistente, revogada ou expirada",
    "como_corrigir": "confira a chave; se houve rotação, a anterior vale por 24h",
    "request_id": "0198f0b2-6e31-7a4c-9f10-2c9a1d4e7b55"
  }
}

3. As rotas que não pedem chave — e por que

Estas rodam dentro do aplicativo instalado no celular e não levam Authorization:

  • registro e atualização de aparelho;
  • recibo de entrega e de clique;
  • pacote e eventos de mensagens In-App;
  • parâmetros públicos do Firebase.

Isso não é um esquecimento; é o desenho. Um aplicativo publicado é um arquivo que qualquer pessoa baixa e inspeciona — uma chave embutida nele é uma chave publicada. O que protege essas rotas no lugar da credencial:

ProteçãoO que faz
Validação do aplicativoo app_id do corpo é conferido antes de qualquer acesso a dado: inexistente ⇒ 400, pausado ⇒ 403.
Validação de formacada campo é checado antes de tocar o banco; pedido malformado nunca custa banco.
Teto por origem120 requisições por minuto por endereço de origem, com Retry-After: 60.
Prova criptográficarecibos e eventos In-App só contam com a prova que o próprio servidor injetou naquele envio, verificada em tempo constante.

Regra prática: pm_live_ só existe no seu servidor. Se você precisa consultar um aparelho a partir do backend, use a rota de consulta autenticada — nunca leve a chave para dentro do aplicativo.


4. A chave de plataforma

Duas rotas — criar aplicativo e rotacionar chave — aceitam Authorization: Bearer <chave de plataforma>. Ela pertence a quem opera o serviço, não ao cliente: no uso normal, você cria o aplicativo e rotaciona a chave pelo painel, que faz exatamente a mesma coisa (inclusive a janela de 24 h).

Três detalhes que economizam depuração:

  • a credencial é conferida antes do corpo. JSON quebrado sem credencial responde 401, não 400 — quem depura pelo corpo procura no lugar errado;
  • se a instância não tem essa chave configurada, a resposta é 503 nomeando a configuração ausente, e ela vem antes do 401;
  • toda tentativa recusada é registrada guardando, no máximo, o identificador da chave — nunca a chave apresentada.

5. O envelope de erro

Um erro que só diz “400 Bad Request” transfere o trabalho para você. Aqui, a resposta de erro é parte do produto: ela diz o que aconteceu, o que fazer agora e carrega o identificador da requisição.

100% dos 4xx/5xx sai neste formato:

{
  "errors": ["requisição inválida: contents é obrigatório (objeto {lang: texto})"],
  "explain": {
    "causa": "contents é obrigatório (objeto {lang: texto})",
    "como_corrigir": "corrija o payload; retry cego não resolve",
    "request_id": "0198f0b2-6e31-7a4c-9f10-2c9a1d4e7b55",
    "doc_url": "https://api.pushmesh.io/docs#erros"
  }
}
CampoO que é
errorsarray de strings. É o campo que integrações clássicas já leem — mantido por compatibilidade.
explain.causao que de fato aconteceu, em uma frase.
explain.como_corrigira ação seguinte. Quando repetir não resolve, ele diz isso.
explain.request_ididentificador desta requisição. Vem também no cabeçalho X-Pm-Request-Id, inclusive nas respostas de sucesso.
explain.doc_urlâncora na documentação. Só aparece quando a instância tem endereço público configurado.

Alguns erros trazem os números que faltavam

O explain é aditivo. Chave de idempotência reusada com corpo diferente, por exemplo, devolve os dois resumos para você comparar:

{
  "errors": ["idempotency_key reused with a different request body"],
  "explain": {
    "causa": "request_hash divergente",
    "como_corrigir": "use uma Idempotency-Key nova para um conteúdo novo",
    "hash_original": "3f2a…",
    "hash_recebido": "9b71…",
    "request_id": "0198f0b2-6e31-7a4c-9f10-2c9a1d4e7b55"
  }
}

E o payload acima do orçamento diz quantos bytes são seus, quantos o servidor injeta e quantos encurtar:

{
  "errors": ["payload renderizado acima do limite: 4210 bytes (máximo 3891)"],
  "explain": {
    "causa": "o payload final FCM/APNs tem 4210 bytes — 4114 da sua mensagem renderizada mais 96 que o servidor injeta no envio (pm_msg_id + pm_rcpt, o rastro que permite recibo de entrega e de clique); o orçamento é 3891 (3,8 KB)",
    "como_corrigir": "encurte contents/headings/data/imagens em pelo menos 319 bytes — o limite é do provedor (4 KB), com margem",
    "bytes": 4210,
    "limite": 3891,
    "bytes_injetados_pelo_servidor": 96
  }
}

Duas ressalvas de leitura

  • errors pode ser objeto — mas só num 200. No envio parcialmente resolvido ele vem como {"invalid_player_ids": [...]}, e a chamada é sucesso. Se você desserializa para um tipo fixo, aceite a união array | objeto.
  • Os textos de errors e explain estão em português, sem negociação de idioma. Ramifique o seu código pelo código HTTP e pelos campos estruturados do explain, nunca pela frase — ela é prosa para humano e pode ser melhorada a qualquer momento.

6. Tabela de códigos

CódigoO que significa aqui
200sucesso. Inclui dois casos que parecem erro: ninguém alcançável (id vazio, recipients: 0, errors array) e envio parcial (errors objeto).
201aplicativo criado.
304o pacote In-App não mudou desde o ETag que você apresentou.
400a forma do pedido está errada: campo obrigatório ausente, tipo errado, valor fora da faixa, campo não suportado. Repetir não resolve.
401problema de credencial: ausente, formato desconhecido, inexistente, revogada, expirada — ou app_id que não é o da chave.
403credencial válida, permissão não: aplicativo pausado, canal In-App desligado, modo de confirmação incompatível com a rota, prova de recibo inválida.
404o recurso não existe dentro do escopo desta chave.
409conflito de estado: outra chamada com a mesma chave de idempotência está processando agora; ou uma campanha In-App em estado que não aceita a operação.
413corpo acima do limite da rota. A causa nomeia o limite.
422forma válida, conteúdo impossível: chave de idempotência reusada com corpo diferente, payload acima de 3.891 bytes, nome reservado em data, campanha In-App inválida — e a conta no plano gratuito acima da franquia de usuários ativos do mês, caso em que o envio é recusado inteiro (seção 8).
429teto estourado (por origem nas rotas do aparelho; por aplicativo nas classes de envio, consulta e exportação).
500falha interna. Repetir com a mesma chave de idempotência é seguro por contrato.
503dependência de configuração ou de infraestrutura ausente. A mensagem nomeia o que falta, sem jamais revelar segredo.

7. Por que a API não confirma o que existe

Esta é a parte que costuma surpreender — e é justamente a que dá confiança a quem entende de segurança.

A API não é oráculo de existência. Se ela respondesse 404 quando a chave é de outro aplicativo, qualquer pessoa com uma chave válida poderia varrer identificadores e descobrir o que existe na plataforma só pela diferença entre 401 e 404. Por isso:

  • 401 é sobre a chave. Inclui o caso “sua chave é boa, mas você pediu um app_id que não é o dela”. Do ponto de vista daquela chave, aquele aplicativo simplesmente não é acessível.
  • 403 é sobre permissão, com a identidade já estabelecida. Aplicativo pausado, canal In-App desligado, ou a rota de consulta num aplicativo que escolheu receber confirmações por webhook — porque confirmação é um canal ou outro, nunca os dois.
  • 404 é sobre o conteúdo do seu próprio escopo. Como todo acesso a dado passa por um escopo de cliente antes de tocar o banco, um recurso de outro cliente é indistinguível de um recurso inexistente. É o mesmo 404, de propósito.

E a mesma lógica vale para as provas criptográficas dos recibos: o 403 de prova inválida tem sempre o mesmo texto, nunca revela o valor esperado, e é avaliado antes de qualquer consulta de existência — senão a rota viraria um oráculo para forjar a prova por tentativa e erro.

{
  "errors": ["proibido"],
  "explain": {
    "causa": "prova de recibo (rcpt) inválida",
    "como_corrigir": "não construa o rcpt no cliente: ele vem pronto no payload do push (data.pm_rcpt) e deve ser ecoado como veio"
  }
}

8. As bordas

Tamanho do corpo

RotasLimiteO que acontece ao passar
Rotas do aparelho (registro, recibo, eventos In-App)16 KB413. Se o Content-Length já declara excesso, a resposta sai sem ler o corpo.
Envio de notificação256 KB413. O limite é maior porque aqui viaja a lista de destinatários (2.000 identificadores ocupam cerca de 80 KB).

O 413 sai no mesmo envelope dos outros erros, com o limite nomeado na causa — nunca um 413 cru de servidor intermediário.

Requisições

TetoOnde valeAo estourar
120 por minuto, por origemrotas do aparelho429 com Retry-After: 60. Requisições recusadas também contam na janela.
6.000 por segundo, por aplicativoenvio de notificação429, cobrado antes de qualquer leitura de dados.
1.000 por segundo, por aplicativoconsulta do resultado de um disparo429. Só conta o que vai ao banco: perguntar o mesmo disparo repetidamente é servido de um cache de 1 segundo e não entra na conta.
1 por segundo, por aplicativoexportação da base de aparelhos429. O arquivo anterior continua valendo — baixe a URL que você já recebeu.
50.000 registros de aparelho por hora, por aplicativoregistro de aparelho429, com a janela de uma hora.
10 aparelhos por identificador de usuárioregistro e vínculo de aparelho400 nomeado.

Honestidade sobre o Retry-After: o texto do 429 por aplicativo menciona esse cabeçalho, mas só o 429 por origem (rotas do aparelho) o emite de fato. A regra segura para o seu cliente: honre Retry-After quando ele existir; na ausência, espere o valor citado em explain.como_corrigir (nos tetos por aplicativo, 1 segundo).

Conteúdo da mensagem

O payload final entregue ao provedor não pode passar de 3.891 bytes. Esse orçamento inclui 96 bytes que o servidor injeta depois da sua chamada — o identificador do disparo e a prova de recibo, isto é, exatamente o que permite medir a entrega real. A verificação acontece na porta, antes de qualquer gravação, e o erro diz quantos bytes encurtar.

A borda comercial: a franquia do plano gratuito

Esta é a única borda desta página que não é técnica — e a única que recusa o envio inteiro. Ela merece um parágrafo porque surpreende quem trata 422 como “pedido malformado” e desiste.

O plano gratuito entrega dentro de uma franquia de usuários ativos no mês. Acima dela, o POST /api/v1/notifications responde 422 e nada é enviado — nem para uma parte da lista. Truncar seria pior: você veria “campanha enviada” e parte da sua base simplesmente não receberia, sem sinal nenhum.

A recusa é escrita para você agir sem abrir chamado. O explain vem com os números:

{
  "errors": ["plano Free atende até 1000 usuários ativos no mês — sua conta está com 1240"],
  "explain": {
    "causa": "o plano Free entrega dentro da franquia de 1000 usuários ativos no mês, e em 2026-08 a sua conta registrou 1240; acima da franquia o envio é recusado INTEIRO — entregar só para uma parte da base, escolhida por ordem de consulta, seria sumir com o resto em silêncio",
    "como_corrigir": "mude para o Premium (USD 0.005 por usuário ativo acima da franquia, sem mensalidade) e o envio volta na hora — os 1000 primeiros usuários ativos continuam gratuitos nos dois planos",
    "plano": "free",
    "mau": 1240,
    "mau_gratis": 1000,
    "mes": "2026-08",
    "moeda": "USD",
    "preco_por_mau": "0.005",
    "medido_ha_s": 12
  }
}
Campo do explainO que é
planoo plano da conta hoje
mauusuários ativos medidos no mês
mau_gratisa franquia
meso mês medido, em AAAA-MM
moeda, preco_por_mauo preço vigente por usuário ativo acima da franquia
medido_ha_sa idade da medição, em segundos

Quatro coisas que valem saber antes de escrever o tratamento desse erro:

  • Repetir não resolve. É 422 justamente por isso, e não 429: esperar não muda o número. A correção é mudar de plano.
  • O número não é tempo real. A contagem varre a base, então é refeita periodicamente em segundo plano — no máximo uma vez por minuto — e medido_ha_s diz com que idade de número você está discutindo.
  • Conta em plano pago nunca é recusada aqui. Ela não entra na medição, então não existe caminho de código que a barre.
  • Falha de infraestrutura não barra ninguém. Sem banco, ou com uma medição velha demais (mais de 600 segundos), a conferência libera o envio. Errar para o lado de entregar é decisão escrita.

O valor da franquia vive na configuração de cobrança e a página de preços é a fonte da verdade; os números do exemplo acima são ilustrativos. O resumo de todos os tetos está em Limites.


9. Repetir ou não repetir

CódigoRepetir a mesma chamada?
400, 422Não. Corrija o pedido; o explain diz o campo. No 422 da franquia do plano gratuito, a correção não é no seu JSON — é no plano.
401, 403Não. Corrija a credencial ou a configuração do aplicativo.
404Não. Confira o identificador.
409Sim, depois de esperar alguns segundos — ou leia o resultado do disparo.
413Não sem encolher o corpo ou dividir o lote.
429Sim, respeitando a espera.
500, 503Sim, com a mesma chave de idempotência — é seguro por contrato.

Sobre a chave de idempotência, o mínimo que você precisa saber agora: mande Idempotency-Key no envio, guarde-a, e reenvie a mesma chave com o mesmo corpo em caso de falha de rede ou 5xx. A resposta original volta idêntica (com o cabeçalho X-Idempotent-Replay: true) por 30 dias, e a mesma chave nunca dispara duas campanhas. Mesma chave com corpo diferente é 422, de propósito. O contrato completo está em Enviar push.

Guarde o request_id das falhas. É ele que liga a sua chamada ao rastro do serviço, e é a primeira coisa que o suporte vai pedir.


Veja também

AssuntoPágina
do zero ao primeiro push com reciboPrimeiros passos
todos os cabeçalhos aceitos e emitidosCabeçalhos
a tabela completa de tetos e limites de produtoLimites
criar o aplicativo e rotacionar a chaveAplicativos e chaves
confirmações assinadas em vez de consultaWebhooks
o que é congelado por contrato e o que pode mudarVersionamento