Aplicativos e chaves
Criar um aplicativo, rotacionar a chave da API e ler os parâmetros públicos do Firebase — as rotas /api/v1/apps.
Apps e chaves
Um app é a unidade de isolamento do PushMesh: os aparelhos, os disparos, as
campanhas e as credenciais de entrega pertencem a um app, e nada atravessa a
fronteira entre dois. O app_id é um UUID; ele é a identidade que o seu
aplicativo carrega e não muda nunca.
Cada app tem uma chave de API — é ela que autentica as chamadas do seu servidor.
Endereço base: https://api.pushmesh.io
Veja também: Autenticação e erros traz o contrato completo das credenciais e a tabela de códigos HTTP.
Antes de mais nada: quem cria o app
Duas destas três rotas — criar app e rotacionar chave — exigem a chave de plataforma, que fica com quem opera o serviço, não com o cliente.
Na prática, se você é cliente do PushMesh:
- Criar o app: no painel, em
https://app.pushmesh.io. Ao criar, a chave aparece uma única vez na tela. - Rotacionar a chave: também no painel, na tela do app.
A referência abaixo existe porque a API é pública e o contrato é o mesmo — e
porque quem roda o PushMesh na própria infraestrutura usa exatamente estas
rotas. Não invente uma chave de plataforma: sem ela, as duas primeiras rotas
respondem 401.
A terceira rota (firebase_params) é diferente: ela é pública de verdade, sem
credencial nenhuma, e é o SDK Android que a chama.
A chave do app
pm_live_<kid>_<segredo> produção
pm_test_<kid>_<segredo> sandbox
kid: 8 caracteres[a-z0-9]. É o índice do lookup — não é segredo.- segredo: 43 caracteres alfanuméricos, ~256 bits de entropia.
Como usar:
Authorization: Basic pm_live_<kid>_<segredo>
Bearer funciona igual. Um cliente HTTP que só monta Basic clássico também
serve: mande Basic <base64 de "qualquer-coisa:pm_live_..."> — o que vem antes
dos dois-pontos é ignorado.
A chave crua nunca é guardada. No banco fica apenas o sha256 dela. Isso
tem uma consequência prática que vale repetir: se você perder a chave, ela não
é recuperável — só resta rotacionar.
A resolução da chave é cacheada por 60 segundos. Uma chave revogada para de funcionar em menos de um segundo, porque a revogação limpa os caches; uma chave que expirou pelo relógio da rotação pode continuar sendo aceita por até um minuto além do vencimento.
POST /api/v1/apps — criar um app
Autenticação: chave de plataforma.
Authorization: Bearer <CHAVE_DE_PLATAFORMA>
A comparação é feita em tempo constante — tentar adivinhar a chave medindo o
tempo de resposta não funciona. Toda tentativa recusada fica registrada na
auditoria, sem a chave tentada: guardamos no máximo o kid, e só quando a
credencial apresentada tem o formato pm_*.
curl -X POST https://api.pushmesh.io/api/v1/apps \
-H "Authorization: Bearer CHAVE_DE_PLATAFORMA" \
-H "Content-Type: application/json" \
-d '{
"nome": "Meu aplicativo",
"rate_por_min": 60000,
"sandbox": false
}'
Campos
| Campo | Tipo | Obrigatório | Comportamento |
|---|---|---|---|
nome | string | sim | espaços das pontas são removidos. Vazio (ou só espaços) ⇒ 400 nome não pode ser vazio. É rótulo de painel: não vai para o aparelho e não entra em nenhuma chave |
rate_por_min | inteiro | não | ritmo máximo de entrega por minuto deste app. Ausente ⇒ 60000. Zero ou negativo ⇒ 400 rate_por_min deve ser positivo |
sandbox | booleano | não | true gera chave com prefixo pm_test_; ausente ou false gera pm_live_ |
O que rate_por_min significa: é o ritmo com que o serviço entrega as
notificações deste app aos provedores (Google e Apple), respeitado pelo
despachante. Não é um teto de requisições HTTP à API.
Resposta
201 Created
{
"id": "0198f3c1-4a2b-7c3d-8e9f-0a1b2c3d4e5f",
"nome": "Meu aplicativo",
"api_key": "pm_live_a1b2c3d4_XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX",
"kid": "a1b2c3d4",
"receipt_key": "…64 caracteres hex…",
"rate_por_min": 60000,
"criado_em": "2026-08-27T14:00:00+00:00"
}
api_keyereceipt_keyaparecem uma única vez, aqui. Não há rota que as devolva de novo. Guarde as duas no seu cofre antes de fechar a resposta.receipt_keyé o segredo que prova os recibos de entrega. Ela é guardada cifrada do nosso lado e nunca sai do servidor depois desta resposta.criado_emé ISO 8601 com fuso.
Erros
| Status | Quando |
|---|---|
400 | corpo não é JSON válido; nome vazio; rate_por_min menor ou igual a zero |
401 | Authorization ausente ou com chave de plataforma incorreta |
503 | chave de plataforma não configurada na instância; cofre de segredos não configurado; banco indisponível |
A ordem importa: a credencial é conferida antes do corpo. Um JSON quebrado
enviado sem credencial devolve 401, não 400.
POST /api/v1/apps/{id}/rotate_key — trocar a chave
Autenticação: chave de plataforma.
curl -X POST https://api.pushmesh.io/api/v1/apps/APP_ID/rotate_key \
-H "Authorization: Bearer CHAVE_DE_PLATAFORMA" \
-H "Content-Type: application/json" \
-d '{"sandbox": false}'
O corpo é opcional. sandbox: true faz a nova chave sair com prefixo
pm_test_; ausente ou false, sai pm_live_. O prefixo não muda mais nada:
é uma marcação para você separar ambientes.
Resposta
200 OK
{
"id": "0198f3c1-4a2b-7c3d-8e9f-0a1b2c3d4e5f",
"api_key": "pm_live_e5f6a7b8_XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX",
"kid": "e5f6a7b8",
"anterior_expira_em": "2026-08-28T14:00:00+00:00"
}
A janela de 24 horas
Rotação nunca produz 401. A chave anterior não morre no instante da troca:
ela recebe uma validade de 24 horas e continua sendo aceita até lá. O campo
anterior_expira_em diz exatamente quando ela para de valer (null se não
havia chave anterior).
O que isso significa: você troca a chave, atualiza os seus serviços com
calma ao longo do dia e nenhuma chamada em voo quebra. Passadas as 24 horas, a
chave velha responde 401 chave inexistente, revogada ou expirada.
Some a esse prazo o cache de resolução de 60 segundos: na virada do vencimento, uma chamada com a chave velha pode ainda passar por até um minuto.
Erros
| Status | Quando |
|---|---|
401 | chave de plataforma ausente ou incorreta |
404 | não existe app com esse {id} |
500 | falha ao gravar a nova chave |
503 | chave de plataforma não configurada na instância; banco indisponível |
Um {id} fora do formato UUID é recusado antes do handler, com 400 e corpo de
um corpo que não segue o envelope de erro padrão.
GET /api/v1/apps/{id}/firebase_params — parâmetros públicos do Firebase
Autenticação: nenhuma. Esta rota é pública por desenho.
O cliente cola o google-services.json uma vez no painel e o aplicativo Android
deixa de precisar do arquivo embutido: o SDK busca os valores aqui, no boot.
Onde conseguir esse arquivo — e a credencial que de fato envia — está em
Credenciais de entrega.
Por que pode ser pública: os cinco valores devolvidos já vão dentro de todo APK publicado. Eles identificam o projeto Firebase; eles não autorizam enviar push. Não são credencial, e por isso não são tratados como uma.
curl https://api.pushmesh.io/api/v1/apps/APP_ID/firebase_params
Resposta
200 OK, quando o app tem os parâmetros configurados:
{
"android": {
"project_id": "meu-projeto-firebase",
"sender_id": "123456789012",
"app_id": "1:123456789012:android:abcdef0123456789",
"api_key": "AIza…",
"package_name": "com.exemplo.meuapp"
},
"ios": null
}
Quando ainda não foram configurados, o mesmo 200 com "android": null.
| Campo | O que é |
|---|---|
project_id | o projeto Firebase |
sender_id | o remetente numérico do projeto |
app_id | o identificador do app Android no Firebase, no formato 1:<número>:android:<hash> |
api_key | a chave de API do Firebase presente no google-services.json |
package_name | o pacote do aplicativo Android |
ios | campo reservado. Sempre null nesta fase — o formato já nasce estável para o SDK não precisar mudar quando o iOS chegar |
Atenção ao nome. O
api_keyde dentro do blocoandroidé a chave do Firebase, pública, do arquivo do Google. Ela não é a chave do app no PushMesh (pm_live_…). São coisas diferentes com nomes parecidos, e confundir as duas é o engano mais caro que dá para cometer aqui.
Limite
Esta rota compartilha o mesmo teto das rotas de aparelho: 120 requisições por
minuto por IP de origem, janela fixa de 60 segundos. Estourou, 429 com
Retry-After: 60.
Erros
| Status | Quando |
|---|---|
403 | app pausado |
404 | não existe app com esse {id} |
429 | teto por IP |
503 | banco indisponível |
Repare na diferença: aqui um app_id inexistente devolve 404. Nas rotas de
aparelho, o mesmo caso devolve 400 app_id inexistente, porque lá o app_id é
um campo do corpo e não o recurso da URL.
Armadilhas da primeira integração
- Tentar criar o app pela API sendo cliente. Criar app e rotacionar chave pedem a chave de plataforma, que fica com quem opera o serviço. Cliente cria e rotaciona no painel.
- Não guardar a chave na hora.
api_keyereceipt_keyaparecem uma única vez, na resposta da criação. Não existe rota para recuperá-las: no banco só mora o hash. - Confundir o
api_keydo Firebase com a chave do app. O primeiro é público e vem dogoogle-services.json. O segundo começa compm_live_e nunca pode sair do seu servidor. - Achar que rotacionar derruba a chave antiga na hora. Ela vale por mais 24 horas, de propósito, para a virada não quebrar nada em voo. Se a sua exigência for cortar o acesso imediatamente, rotacionar não é o caminho.
- Esperar o envelope de erro num
{id}malformado. Um{id}que não é UUID é recusado pelo framework, com400e corpo de texto simples. - Esperar
200na criação. O sucesso é201. - Ler
rate_por_mincomo teto de API. É ritmo de entrega por minuto, não limite de requisições HTTP. - Mandar credencial errada e ler o erro do corpo. A credencial é conferida
primeiro: com ela errada, você recebe
401mesmo que o JSON também esteja quebrado. - Esperar dados de iOS no
firebase_params. O campoiosé reservado e hoje é semprenull.