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

Índice

Aparelhos

Registrar, identificar o usuário, atualizar, consultar e exportar aparelhos — com a regra que derruba a maioria das primeiras integrações.

Aparelhos

Um aparelho é um celular ou navegador capaz de receber um push. Cada um tem um identificador público — o player_id, um UUID — e é por ele que você mira um disparo.

Estas rotas fazem quatro coisas: registrar um aparelho, dizer quem está usando ele, consultar o estado dele e exportar a base inteira.


Em 30 segundos

curl -X POST https://api.pushmesh.io/api/v1/players \
  -H 'Content-Type: application/json' \
  -d '{
    "app_id": "01926f3a-4b2c-7d8e-9f01-23456789abcd",
    "device_type": 1,
    "identifier": "TOKEN-DE-PUSH-DO-APARELHO",
    "external_user_id": "usuario-4711"
  }'
{ "success": true, "id": "01926f4c-7a1b-7c2d-8e3f-a1b2c3d4e5f6" }

O id da resposta é o player_id. Guarde-o: é ele que vai em include_player_ids no envio e é ele que o aparelho manda de volta no recibo de entrega.

A resposta é 200, nunca 201 — inclusive no primeiro registro. A rota é idempotente por (app, token): chamar cem vezes com o mesmo token devolve sempre o mesmo player_id.


As quatro rotas, e quem chama cada uma

RotaQuem chamaCredencial
POST /api/v1/playerso app, no aparelhonenhuma
PUT /api/v1/players/{id}o app, no aparelhonenhuma
GET /api/v1/players/{id}o seu servidorchave do app
POST /api/v1/players/csv_exporto seu servidorchave do app

As duas primeiras são públicas de propósito: um aplicativo publicado é um segredo publicado, então a chave da API nunca precisa entrar no APK ou no IPA. O que protege essas rotas no lugar da chave:

  • o app_id do corpo é conferido contra a base antes de qualquer escrita — inexistente responde 400, aplicativo pausado responde 403;
  • validação de forma campo a campo, antes de tocar o banco;
  • corpo de no máximo 16 KB;
  • teto de 120 requisições por minuto por IP de origem, com Retry-After: 60.

As duas últimas exigem a chave do app no cabeçalho Authorization, e o app_id da query tem de ser o mesmo app da chave — se não for, a resposta é 401, nunca 404 (a API não confirma a existência de recurso de outro cliente).

Authorization: Basic pm_live_a1b2c3d4_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx

1. Registrar um aparelho

POST https://api.pushmesh.io/api/v1/players

Chame a cada abertura do app. A rota faz um upsert: token novo cria, token conhecido atualiza, e o player_id devolvido é sempre o mesmo.

Campos obrigatórios

CampoTipoLimiteO que faz
app_idUUIDO aplicativo. Precisa existir e estar ativo.
device_typeinteiro0, 1, 5, 7 ou 8Plataforma: 0 = iOS, 1 = Android, 5 = Chrome/web, 7 = Safari, 8 = Firefox. Outro valor é recusado pelo nome.
identifierstring1 a 194 bytesO token de push do aparelho. É a chave de deduplicação.

O teto de 194 bytes do identifier existe para gritar na porta: um token real tem de 64 a ~170 caracteres, e um valor maior é quase sempre o payload inteiro colado no campo errado. O erro diz quantos bytes chegaram.

Campos opcionais de identidade e conteúdo

CampoTipoLimiteO que faz
external_user_idstring128 bytesSeu identificador de pessoa (id de conta, e-mail hasheado…). Permite mirar por usuário em vez de por aparelho. Máximo 10 aparelhos por valor.
app_versionstringVersão do seu app. Aceita também o nome legado game_version; se vierem os dois, app_version vence.
device_modelstringModelo do aparelho, como texto livre.
tagsobjeto JSON2 KB serializadosPares chave/valor seus. Precisa ser objeto — lista ou string é recusada.
languagestring32 bytesCódigo curto do idioma ("pt", "en").
timezoneinteiro±86400Offset do fuso em segundos (ex.: -10800). Nome de fuso aqui é erro.
transportestring"fcm" ou "apns"Por qual caminho o token deve ser entregue. Ausente: aparelho novo nasce fcm, aparelho existente mantém o que já tinha.

Campos opcionais de perfil

São todos best-effort: valor estranho nunca derruba o registro, e campo ausente nunca apaga o que já estava gravado. Servem para você entender a base sem pedir nenhuma permissão nova ao usuário.

CampoTipoObservação
device_osstringCortado em 64 bytes.
fabricantestringCortado em 64 bytes.
sdk_versaostringUsado para saber quem já pode receber recursos novos.
timezone_idstringAqui sim vai o nome do fuso.
paisstringCortado em 64 bytes.
net_typestringTipo de rede.
carrierstringOperadora.
standby_bucketstringEstado de economia de bateria do sistema.
instalacao_idstringIdentificador estável da instalação. Ver abaixo — vale ouro.
rootedbooleano
bateria_irrestritabooleano
sessoesinteiroContador de sessões. Nunca diminui: o servidor guarda o maior valor já visto. Negativo é tratado como ausente.
notif_permissoesinteiroMáscara de bits da permissão de notificação. Negativo é tratado como ausente.

Resposta

{ "success": true, "id": "01926f4c-7a1b-7c2d-8e3f-a1b2c3d4e5f6" }

A regra que derruba a primeira integração

No registro, campo omitido APAGA o valor gravado.

Vale para seis campos: external_user_id, app_version, device_model, tags, language e timezone. O registro grava exatamente o retrato que você mandou — inclusive o vazio.

O acidente clássico: o app manda external_user_id no login, e na abertura seguinte registra sem ele. O vínculo some, e a base “perde” os usuários sem nenhum erro aparecer.

Grupo de camposCampo ausente…
external_user_id, app_version, device_model, tags, language, timezoneapaga o gravado
transporte e todos os campos de perfilpreserva o gravado

Duas formas de não cair nisso:

  1. no registro, mande sempre o retrato completo do aparelho (é o que o SDK faz);
  2. para mudar uma coisa só, use o PUT — lá a regra é a oposta.

Registrar ressuscita token morto

Se o aparelho tinha sido marcado como inválido (o provedor rejeitou o token) e ele volta a se registrar com o mesmo token, ele volta a valer, e o motivo da invalidação é apagado. Quem espelha a nossa base do lado de lá precisa reler periodicamente — senão fica com um aparelho marcado como morto para sempre.

instalacao_id: o fim dos aparelhos fantasma

Reinstalar o app gera um token novo, e o token antigo continua parecendo vivo até algum disparo falhar nele. Resultado: uma base inflada de aparelhos inalcançáveis que você conta como se fossem gente.

Mandando instalacao_id — um identificador estável da instalação — o servidor sabe na hora que é o mesmo aparelho e aposenta os tokens anteriores dele no próprio registro, sem esperar disparo nenhum.

Aparelhos de teste

Um identifier que começa com test: cria um aparelho de sandbox. Aparelho de sandbox nunca entra em público de disparo — nem quando o id dele está explícito na lista — e não aparece na exportação. É por isso que ele volta em invalid_player_ids no envio: não é defeito, é a proteção funcionando.


2. Login, logout e ajuste pontual

PUT https://api.pushmesh.io/api/v1/players/{player_id}

É a rota de vínculo pessoa ↔ aparelho.

# login
curl -X PUT https://api.pushmesh.io/api/v1/players/01926f4c-7a1b-7c2d-8e3f-a1b2c3d4e5f6 \
  -H 'Content-Type: application/json' \
  -d '{"app_id":"01926f3a-4b2c-7d8e-9f01-23456789abcd","external_user_id":"usuario-4711"}'

# logout
curl -X PUT https://api.pushmesh.io/api/v1/players/01926f4c-7a1b-7c2d-8e3f-a1b2c3d4e5f6 \
  -H 'Content-Type: application/json' \
  -d '{"app_id":"01926f3a-4b2c-7d8e-9f01-23456789abcd","external_user_id":null}'
{ "success": true }
CampoTipoObrigatórioO que faz
app_idUUIDsimO aplicativo do aparelho.
external_user_idstring, null ou ausentenãoTrês estados — ver abaixo.
tagsobjeto JSONnãoAté 2 KB serializados.
app_versionstringnãoAceita o nome legado game_version.
transportestringnão"fcm" ou "apns".

Os três estados do external_user_id

No corpoSignificado
campo ausentenão mexe no vínculo
"usuario-4711"login: define o vínculo
null explícitologout: limpa o vínculo

O logout só acontece com null escrito. Omitir o campo não desloga ninguém — e essa é a segunda causa mais comum de “a pessoa saiu da conta e continuou recebendo”.

Aqui, campo ausente NÃO apaga

No PUT, app_version, tags e transporte preservam o valor gravado quando não vêm no corpo. É a regra oposta à do registro: leia a do verbo que você está usando, não a do outro.


3. Quando o mesmo aparelho troca de usuário

É o ponto onde mais integração se machuca, e vale escrever a receita inteira.

Um aparelho é um objeto físico; um external_user_id é uma pessoa. O aparelho sobrevive à troca de pessoa — e se você não contar isso ao servidor, a pessoa nova recebe as mensagens da pessoa antiga.

A receita:

  1. No login, chame PUT com external_user_id igual ao id da pessoa.
  2. No logout, chame PUT com external_user_id: null. Explicitamente.
  3. A cada abertura do app, ao registrar, mande de novo o external_user_id se a pessoa continua logada. Registro sem ele apaga o vínculo (seção 1).
  4. Não invente um external_user_id por aparelho. Ele existe para juntar os aparelhos de uma mesma pessoa; um valor por aparelho não junta nada e ainda consome o teto.

O teto: no máximo 10 aparelhos por external_user_id. Ele é cobrado no registro e também no PUT — mas só quando o vínculo muda. Reenviar o mesmo valor que o aparelho já tem passa sempre, então um SDK que reafirma o login a cada abertura nunca esbarra no teto. Chegando ao 11º aparelho, a resposta é 400 máximo 10 devices por external_user_id.


4. Consultar um aparelho

GET https://api.pushmesh.io/api/v1/players/{player_id}?app_id=…

Do seu servidor, com a chave do app. É por aqui que a sua integração decide se vale a pena gastar um disparo com aquele aparelho.

curl 'https://api.pushmesh.io/api/v1/players/01926f4c-7a1b-7c2d-8e3f-a1b2c3d4e5f6?app_id=01926f3a-4b2c-7d8e-9f01-23456789abcd' \
  -H 'Authorization: Basic pm_live_a1b2c3d4_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx'
{
  "id": "01926f4c-7a1b-7c2d-8e3f-a1b2c3d4e5f6",
  "identifier": "TOKEN-DE-PUSH-DO-APARELHO",
  "device_type": 1,
  "notification_types": 1,
  "invalid_identifier": false,
  "external_user_id": "usuario-4711",
  "app_version": "3.2.0",
  "device_model": "Modelo X",
  "tags": { "plano": "premium" },
  "last_active": 1756300000,
  "created_at": 1756200000,
  "playtime": 0,
  "session_count": 0,
  "device_os": null,
  "language": "pt",
  "timezone": -10800
}
CampoO que é
identifierO token de push, como foi gravado.
notification_typesEstado de inscrição — ver a tabela abaixo.
invalid_identifiertrue quando o token está morto (o provedor rejeitou ou ele foi substituído).
last_activeÚltimo sinal de vida, em epoch de segundos.
created_atPrimeiro registro, em epoch de segundos.
tagsVolta {} quando não há tags, nunca null.
playtime, session_count, device_osConstantes de formato: sempre 0, 0 e null. Existem para compatibilidade de shape, não são medições. Não monte métrica em cima deles.

notification_types, sem maquiagem

O contrato do campo tem quatro estados:

Estado do aparelhonotification_types
válido e inscrito1
desinscreveu ou bloqueou-2
nunca concedeu permissão0
token mortoúltimo valor conhecido, com invalid_identifier: true

E a parte honesta: hoje nenhuma rota desta API escreve os estados 0 e -2. Um aparelho registrado nasce inscrito e assim permanece; o que muda ao longo da vida dele é o invalid_identifier. Se você quer uma pergunta única e verdadeira para “consigo alcançar este aparelho?”, a resposta é invalid_identifier === false.

Se o seu app sabe que o usuário desligou as notificações no sistema, essa informação chega ao servidor pelo campo de perfil notif_permissoes do registro — não pelo notification_types.


5. Exportar a base

POST https://api.pushmesh.io/api/v1/players/csv_export?app_id=…

A foto de quem está vivo, em CSV comprimido, para a sua integração conferir a base antes de gastar disparo.

curl -X POST 'https://api.pushmesh.io/api/v1/players/csv_export?app_id=01926f3a-4b2c-7d8e-9f01-23456789abcd' \
  -H 'Authorization: Basic pm_live_a1b2c3d4_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx'
{
  "csv_file_url": "https://…/exports/01926f3a-…/players-20260827T143000Z-…csv.gz",
  "pm_pronto_em_s_estimado": 30,
  "pm_validade_h": 72
}

A resposta é imediata e o arquivo é gerado em segundo plano. O laço do seu lado é simples: tente baixar a URL; enquanto o arquivo não estiver pronto ela responde 404, e responde 200 quando estiver.

O arquivo é um CSV comprimido com cinco colunas:

id,external_user_id,notification_types,invalid_identifier,last_active
  • last_active é o último sinal de vida real do aparelho (sessão do app ou recibo de entrega), em epoch de segundos — é com ele que você pula aparelho inativo;
  • aparelhos de sandbox ficam de fora;
  • vírgulas e aspas dentro de um campo são escapadas no padrão CSV, então qualquer parser de mercado lê sem tratamento especial.

Três coisas a saber antes de usar em produção:

  1. A URL é a credencial. O arquivo carrega external_user_id, que na maior parte das integrações identifica uma pessoa. O que protege o arquivo é o nome ser impossível de adivinhar. URL em log, em ticket ou em chat é vazamento.
  2. pm_validade_h: 72 é janela de contrato. Baixe o arquivo dentro dela, guarde do seu lado e não use a URL como armazenamento permanente.
  3. Um export por segundo, por aplicativo. Estourou, a resposta é 429 — e a orientação é reusar a URL anterior, que continua valendo, em vez de pedir outro. Este 429 não traz o cabeçalho Retry-After; espere um segundo.

6. Como um aparelho sai da base

Não existe rota pública para apagar um aparelho, e a ausência é deliberada: as rotas de aparelho são públicas, e uma rota pública capaz de apagar seria uma rota pública capaz de esvaziar a sua base.

O que de fato tira um aparelho do alcance:

AcontecimentoEfeito
O provedor rejeita o token no envioO aparelho passa a invalid_identifier: true e sai de todos os públicos.
O app é reinstalado (com instalacao_id)O token anterior é aposentado no próprio registro do token novo.
Você apaga aparelhos pelo painelO aparelho e o histórico de entregas dele são removidos juntos, na mesma transação.

Vale saber: apagar não é banir. Um aparelho vivo que você apagou volta a se registrar sozinho no próximo uso do app. Para parar de falar com alguém, a ferramenta certa é o vínculo (logout) ou a sua própria regra de público, não a exclusão.

Se o que você precisa é atender a um pedido de exclusão de um titular — e saber quanto tempo cada camada guarda o quê —, a receita completa está em Dados, retenção e privacidade.


Erros

Todo erro sai no mesmo envelope:

{
  "errors": ["requisição inválida: tags deve ser um objeto JSON"],
  "explain": {
    "causa": "tags deve ser um objeto JSON",
    "como_corrigir": "corrija o payload; retry cego não resolve",
    "request_id": "01926f5a-0000-7000-8000-000000000000"
  }
}

Toda resposta — de sucesso ou de erro — traz o cabeçalho X-Pm-Request-Id. É o fio que liga a sua chamada ao nosso registro de serviço; cite-o no suporte.

CódigoQuando acontece nestas rotas
400JSON inválido; device_type ausente ou não suportado; identifier vazio ou acima de 194 bytes; tags que não é objeto ou passa de 2 KB; language acima de 32 bytes; timezone que não é inteiro ou está fora de ±86400; transporte diferente de fcm/apns; external_user_id acima de 128 bytes; app_id inexistente; 11º aparelho no mesmo external_user_id; app_id ausente na query das rotas de servidor.
401Cabeçalho Authorization ausente ou em formato desconhecido; chave inexistente, revogada ou expirada; app_id da query que não é o app da chave.
403Aplicativo pausado.
404player_id inexistente neste aplicativo.
413Corpo acima de 16 KB.
429Mais de 120 requisições por minuto do mesmo IP (com Retry-After: 60); mais de 50.000 registros por hora no mesmo aplicativo; mais de um export por segundo.
503Banco indisponível; armazenamento de exportação não configurado no ambiente.

Ramifique sempre pelo código HTTP. Os textos de causa e como_corrigir são prosa para humano, saem em português e podem mudar.


Limites, em números

LimiteValorOnde vale
Corpo da requisição16 KBrotas chamadas pelo aparelho
identifier194 bytesregistro
external_user_id128 bytesregistro e login
tags (JSON serializado)2 KBregistro e atualização
language32 bytesregistro
timezone±86.400 segundosregistro
Aparelhos por external_user_id10registro e login
Registros por hora, por aplicativo50.000registro
Requisições por minuto, por IP120rotas chamadas pelo aparelho
Exportações1 por segundo, por aplicativoexport

O teto por IP é contado por instância do serviço e depende do IP de origem repassado pela borda. Trate-o como uma rede de proteção contra laço maluco, não como um número para calibrar retry fino.