Pular para o conteúdo

OAuth 2.1

Como uma aplicação obtém um token em nome de uma pessoa, o que esse token pode fazer e todos os erros que ele pode receber.

O Pingo Notify roda o próprio servidor de autorização OAuth 2.1. É dele que sai a credencial de software que age em nome de uma pessoa — um assistente de IA, uma integração de terceiro, qualquer coisa a que você prefira não entregar um token de API.

Qual credencial usar

Token de APIToken OAuth 2.1
Enviado comoapikey: sk_live_…Authorization: Bearer …
Identidadeo membro que o crioua pessoa que aprovou a tela de consentimento
Workspaceo do próprio token; X-Account-Id trocafixado no consentimento; X-Account-Id é recusado
Permissõestudo que o criador pode fazersó os escopos aprovados, cruzados com o papel daquela pessoa
Validadenão expira — revogado à mão1 hora, renovado por refresh token
Serve parao seu próprio backendsoftware que não é seu, ou que muita gente instala

Nunca envie os dois headers. Num endpoint que espera um, o outro é ignorado, não somado.

O fluxo

Cinco chamadas. 1 e 2 acontecem uma vez por aplicação, 3 uma vez por pessoa que autoriza, 4 em toda requisição, 5 mais ou menos a cada hora.

Sua aplicaçãoo seu servidorA pessoanavegador delaPingo Notify api.pingonotify.com uma vez, antes de tudoregistra o cliente → recebe client_id1manda para /v3/oauth/authorize2entra na conta e aprova os escopos3302 com code · vale 60 s, uma vez4cai na sua redirect_uri5troca o código — POST /v3/oauth/token6access_token (1 h) + refresh_token7chama a API com Authorization: Bearer8a resposta, no escopo aprovadoa senha existe só aqui dentrodaqui em diante, sem a pessoa
A senha nunca cruza para a raia da esquerda. O que atravessa é um código de 60 segundos e, depois, um token — preso ao workspace que a pessoa escolheu e limitado aos escopos que ela aprovou.

1 — Registre o cliente

POST /v3/oauth/register (RFC 7591). Aberto, sem credencial, responde 201.

curl -X POST https://api.pingonotify.com/v3/oauth/register \
  -H "Content-Type: application/json" \
  -d '{
    "client_name": "Assistente da Acme",
    "redirect_uris": ["https://acme.example/oauth/callback"],
    "token_endpoint_auth_method": "none",
    "grant_types": ["authorization_code", "refresh_token"],
    "scope": "profile connections:read messages:send"
  }'
bash
CampoObrigatórioValor
redirect_urissimde 1 a 10 URIs, casadas exatamente no /authorize. Absolutas, sem fragmento, sem credenciais. https sempre; http só em loopback (localhost, 127.0.0.1, [::1]); esquema de app nativo vale se tiver ponto (com.acme.app:/oauth)
client_namenãoAparece na tela de consentimento, até 255 caracteres. Omita e o seu app aparece como Aplicativo sem nome — mande sempre
client_urinãoAparece na tela de consentimento como o site do app
logo_urinãoURL de imagem. É o rosto do app na tela de consentimento
scopenãoTeto de escopos deste cliente, separados por espaço. Omitido concede todos os pedíveis
grant_typesnãoauthorization_code, refresh_token. Omitido significa só authorization_code
response_typesnãocode é o único valor aceito
token_endpoint_auth_methodnãonone, client_secret_basic ou client_secret_post. Omitido cria cliente confidencial
software_id, software_versionnãoAceitos e validados, depois ignorados: não são gravados nem devolvidos
{
  "client_id": "xf3K9…",
  "client_id_issued_at": 1780000000,
  "client_secret_expires_at": 0,
  "client_name": "Assistente da Acme",
  "redirect_uris": ["https://acme.example/oauth/callback"],
  "grant_types": ["authorization_code", "refresh_token"],
  "response_types": ["code"],
  "token_endpoint_auth_method": "none",
  "scope": "profile connections:read messages:send"
}
json

client_secret só aparece para cliente confidencial, e só nesta resposta.

Duas omissões custam uma tarde cada.

  • Deixe grant_types de fora e o cliente fica só com authorization_codenenhum refresh token é emitido, e a sua integração morre uma hora depois precisando de gente.
  • Deixe token_endpoint_auth_method de fora e você recebe um cliente confidencial, com um client_secret mostrado uma única vez e que passa a ser exigido em toda chamada de token. Mande "none" para ser um cliente público, que se prova por PKCE.

scope omitido concede ao cliente todos os escopos pedíveis. Registrar não é aprovar: um cliente registrado assim não tem dono nem workspace até que alguém o autorize.

2 — Mande a pessoa para o /authorize

GET /v3/oauth/authorize, no navegador. Não é chamada de API — responde sempre 302.

ParâmetroObrigatórioValor
response_typesimcode — o único valor aceito
client_idsimvindo do passo 1
redirect_urise o cliente registrou mais de umatem de casar exatamente com uma registrada
code_challengesimbase64url de SHA-256(code_verifier)
code_challenge_methodsimS256 — explícito; plain não existe aqui
scopenãoseparados por espaço; o padrão é profile
staterecomendadodevolvido intacto
resourcenãoa URL exata para a qual o token vale — veja Audiência
promptnãoconsent força a tela; none proíbe qualquer interação

PKCE é obrigatório para todo cliente, inclusive os confidenciais.

A pessoa entra na conta, escolhe um workspace e aprova. Aí o navegador cai na sua redirect_uri com code, state e iss.

Só um Owner ou um Admin pode autorizar uma aplicação para um workspace. A tela oferece apenas os workspaces em que o papel da pessoa permite. Aprovar sem escolher um amarra o token à conta pessoal dela.

A pessoa pode aprovar menos escopos do que você pediu. Leia o scope que volta no passo 3 — ele é a verdade; o seu pedido era um desejo.

3 — Troque o código

POST /v3/oauth/token, em application/x-www-form-urlencoded ou JSON.

curl -X POST https://api.pingonotify.com/v3/oauth/token \
  -H "Content-Type: application/x-www-form-urlencoded" \
  -d grant_type=authorization_code \
  -d code=aG9sYS4uLg \
  -d redirect_uri=https://acme.example/oauth/callback \
  -d client_id=xf3K9... \
  -d code_verifier=dBjftJeZ4CVP...
bash
{
  "access_token": "eyJ…",
  "token_type": "Bearer",
  "expires_in": 3600,
  "refresh_token": "8xLOxBt…",
  "scope": "profile connections:read messages:send"
}
json

refresh_token só aparece se o cliente registrou esse grant. Cliente público não manda client_secret; confidencial manda, por HTTP Basic ou como campo do formulário.

O código vale 60 segundos e uma vez só. Repetir o seu próprio código revoga a concessão inteira — isso é detecção de reuso, não defeito.

4 — Chame a API

curl https://api.pingonotify.com/v3/connections \
  -H "Authorization: Bearer eyJ…"
bash

Três regras que não têm equivalente do lado do token de API:

  • O workspace é fixo. Mandar X-Account-Id de outro workspace responde 403 — This access token is bound to a different workspace; re-authorize to switch.
  • Escopo é teto, nunca concessão. A permissão efetiva é a interseção entre os escopos aprovados e o que o papel daquela pessoa permite. Um token com helpdesk:write na mão de um Agent continua fazendo só o que um Agent faz.
  • resource prende o token a um caminho. Peça https://api.pingonotify.com/v3/mcp e o token é aceito sob /v3/mcp e responde 401 em todo o resto. Omita resource e o token vale na API inteira. Peça o recurso que você de fato pretende chamar.

Qualquer coisa que nenhum escopo cubra está fechada para token OAuth, e ela diz isso: this operation is not available to OAuth clients. A gestão de consentimento é o caso notável — nenhum escopo a alcança, então uma aplicação nunca consegue listar nem revogar o acesso de outra.

5 — Renove

curl -X POST https://api.pingonotify.com/v3/oauth/token \
  -H "Content-Type: application/x-www-form-urlencoded" \
  -d grant_type=refresh_token \
  -d refresh_token=8xLOxBt... \
  -d client_id=xf3K9...
bash

A resposta tem a mesma forma do passo 3, com um refresh token novo. A rotação é obrigatória: o que você apresentou morre no instante em que o par novo é emitido. Guarde o novo antes de usar o access token novo.

Apresentar um refresh token duas vezes revoga a concessão inteira — todos os tokens que aquele cliente tem daquela pessoa naquele workspace, de uma vez, com invalid_grant. É a defesa contra token roubado, e ela não consegue distinguir o seu laço de retry de um ladrão. Nunca renove de dois processos ao mesmo tempo, e nunca repita uma renovação cuja resposta você não leu.

Tanto scope quanto resource só podem estreitar na renovação. Pedir mais devolve invalid_scope ou invalid_target.

Escopos

Doze escopos podem ser pedidos. O legacy existe, mas nenhum cliente pode pedi-lo — ele pertence à ponte do token de API da v1.

EscopoConcede
profileQuem autorizou e em qual workspace. É o padrão quando scope é omitido
connections:readLer conexões e o status delas
connections:connectParear e desconectar um número (QR code e logout), sem poder criar nem apagar conexões
connections:writeCriar, editar, conectar e desconectar conexões. Também satisfaz connections:read
messages:readLer o histórico de mensagens e baixar os anexos recebidos
messages:sendEnviar mensagens: texto, mídia, áudio, figurinha, template, lista e botões
contacts:readLer contatos
contacts:writeCriar e editar contatos
helpdesk:readLer conversas, contatos e configurações do helpdesk
helpdesk:writeResponder e gerenciar o helpdesk. Exige o plano PRO além do escopo
webhooks:readVer webhooks e o histórico de entregas deles
webhooks:writeCriar, editar e excluir webhooks, e ler o segredo de assinatura
stats:readVer estatísticas do workspace

Peça o mínimo. Todo escopo a mais é uma linha na tela de consentimento que a pessoa pode recusar — e recusar estreita a aprovação inteira.

Se o seu aplicativo não funciona sem uma permissão específica, ela pode ser marcada como obrigatória: a caixa aparece travada na tela, com o motivo, e a autorização é recusada se alguém tentar removê-la por baixo. Isso é configurado no cadastro do cliente, não no /authorize — fale com o suporte para o seu caso.

Como o seu app aparece na tela

O usuário vê o nome, o logo e o site que você registrou — e um aviso quando o cliente se registrou sozinho pelo endpoint dinâmico, dizendo que o nome exibido foi escolhido pelo próprio app.

Aplicativos que o Pingo verificou exibem um selo Verificado pelo Pingo no lugar desse aviso. A verificação é concedida pela plataforma e confirma quem publica o aplicativo — ela não muda o que ele pode fazer nem pula o consentimento: as permissões continuam sendo escolhidas pelo usuário, uma a uma. Não há como um aplicativo se autodeclarar verificado.

Revogando

ObjetivoChamadaCredencial
A aplicação descarta o próprio tokenPOST /v3/oauth/revokeo próprio cliente
O workspace lista quem tem acessoGET /v3/oauth/consentstoken de API, ou o painel
O workspace corta uma aplicaçãoDELETE /v3/oauth/consents/{id}token de API, ou o painel

O revoke responde 200 para qualquer entrada, inclusive um token que nunca existiu. Isso é de propósito: responder diferente transformaria o endpoint num oráculo para adivinhar token válido.

Reaprovar uma aplicação com menos escopos também revoga os tokens já emitidos sob a aprovação antiga, para que a tela de consentimento e a realidade nunca discordem.

Erros

Onde o erro chega importa mais que o código dele, porque é isso que decide o que o seu cliente tem de parsear. São três lugares.

Como corpo JSON

O corpo é exatamente { "error": "…", "error_description": "…" } — sem outro envelope, sem campo statusCode.

errorHTTPEndpointCausaConserto
invalid_client_metadata400registerUm campo do registro está errado, grant_types tem valor não suportado, ou o registro dinâmico está desligado neste servidorO error_description nomeia o campo
invalid_redirect_uri400register, authorizeUma redirect URI é relativa, tem fragmento ou credenciais, ou é http fora de loopbackUse URI absoluta https, ou http://localhost
invalid_scope400register, tokenEscopo desconhecido, legacy, fora do que o cliente registrou, ou uma renovação tentou ampliarPeça um subconjunto dos escopos do cliente
invalid_request400authorize, token, revoke, introspectFalta um parâmetro, ou ele está malformado ou é contraditórioLeia o error_description
invalid_client400 no authorize · 401 no token, revoke, introspectclient_id desconhecido, ou o segredo de um cliente confidencial falta ou está errado, ou um cliente público mandou umCliente público não manda segredo
unauthorized_client400tokenO cliente não registrou o grant que está usandoRegistre refresh_token antes de usá-lo
unsupported_grant_type400tokengrant_type não é authorization_code nem refresh_token
invalid_grant400tokenO código expirou (60 s), já foi usado, é de outro cliente, ou um refresh token foi repetidoNo reuso a concessão inteira se foi: recomece no /authorize
invalid_target400tokenresource fora da origem do issuer, com fragmento, ou uma renovação tentou ampliá-loMande a URL exata do recurso, ou nada

Na sua redirect_uri

Assim que client_id e redirect_uri são validados, todo erro restante do authorize vira um 302 para o seu callback, nunca um corpo JSON — responder no corpo antes desse ponto transformaria o endpoint num open redirect. Parseie a query, não o status:

https://acme.example/oauth/callback?error=invalid_scope&error_description=…&state=…&iss=https://api.pingonotify.com
text

O iss existe para você saber qual servidor de autorização respondeu (RFC 9207). Os códigos que chegam por aqui: unsupported_response_type, unauthorized_client, invalid_request, invalid_scope, invalid_target, login_required, consent_required, access_denied, server_error.

access_denied significa que uma pessoa recusou, ou que o papel dela não permite autorizar aquele workspace. Não há o que repetir.

Numa chamada de API

errorHTTPCausaConserto
invalid_token401Falta, expirou, foi revogado, ou foi emitido para outro resourceRenove; se falhar, autorize de novo
insufficient_scope403Falta um escopo ao token, ou nada concede aquela operação a cliente OAuthReautorize com o escopo que a resposta nomeia

Os dois levam um header WWW-Authenticate, e no insufficient_scope ele nomeia o scope que falta:

WWW-Authenticate: Bearer realm="pingo", error="invalid_token",
  error_description="the access token is invalid, expired or revoked",
  resource_metadata="https://api.pingonotify.com/.well-known/oauth-protected-resource/v3/mcp"
http

Siga o resource_metadata em vez de fixar endpoints no código — ele é o fio de volta ao servidor de autorização.

Rate limit responde 429, e o corpo dele não está no formato de erro do OAuth. Trate 429 como transporte, não como erro de protocolo.

Dois códigos existem no vocabulário do OAuth mas este servidor nunca os devolve: interaction_required e temporarily_unavailable. Não escreva tratamento para eles.

Validades e limites

Código de autorização60 segundos, uso único
Access token1 hora — a menos que o cliente desligue a expiração
Refresh token30 dias de inatividade, rotacionado a cada uso. Cada renovação reinicia o relógio
Requisição de autorização pendente10 minutos
POST /v3/oauth/token, /revoke, /introspect120 requisições por minuto, por IP
GET /v3/oauth/authorize, POST /v3/oauth/register15 requisições por minuto, por IP
Documentos de descobertasem limite, cacheados por 5 minutos

Expirar é uma opção por cliente. Um cliente registrado pelo painel pode desmarcar Expirar tokens de acesso — aí o access token não tem prazo, o expires_in não vem na resposta, e nenhum refresh token é emitido, porque refresh existe para renovar o que expira. O registro dinâmico (RFC 7591) não consegue definir isso: cliente que se registra sozinho sempre recebe token com prazo.

Com a expiração desligada, revogar é o único jeito de cortar o acesso — pela tela de Aplicativos conectados ou por POST /v3/oauth/revoke. Mudar a opção vale para tokens novos; os já emitidos seguem com a validade com que nasceram.

Descoberta

Não fixe os endpoints no código. Os dois documentos são públicos, não pedem credencial e são a forma suportada de achar tudo que está acima:

DocumentoResponde
/.well-known/oauth-authorization-serverissuer, todos os endpoints, escopos suportados, grant types, code_challenge_methods_supported, prompt_values_supported
/.well-known/oauth-protected-resourcea API como um todo, e qual servidor de autorização a guarda
/.well-known/oauth-protected-resource/v3/mcpespecificamente o endpoint MCP, e só os escopos que ele usa

A primeira coisa construída sobre tudo isto é o Servidor MCP — um assistente de IA conecta exatamente por este fluxo, e a página dele mostra ponta a ponta. Para a credencial de token de API, veja API Keys e Autenticação.

© 2026 Pingo Notify. Todos os direitos reservados.

pingonotify.com ·Feito com Nuxt e Scalar