Por que seu token falha: escopos que não seguem o nome do recurso

Existe uma categoria de falha de integração que consome uma tarde inteira e termina numa linha de configuração: o token não sai, o erro fala de autorização, e a suspeita natural recai sobre o client_id e o client_secret — que estão certos. A causa costuma ser outra e é fácil de descartar cedo, desde que você saiba de antemão de duas coisas: a API tem exatamente seis escopos, e os nomes deles não acompanham os nomes dos recursos. Quem deduz o escopo a partir da rota — /v1/envelopesenvelopes:write — pede algo que não existe, e a resposta a isso não diz "esse escopo não existe".

As duas frases que resolvem 90% dos casos: a API tem seis escopos e nenhum deles leva o nome de um recurso — /v1/envelopes, /v1/signing-sessions e /v1/users/…/enrollment autorizam todos em transactions:*. E pedir apenas escopos que não existem devolve 400 no endpoint de token, com uma mensagem que parece problema de credencial.

Os seis escopos

Escopo Permite
transactions:write Criar e alterar fluxos de assinatura: transações, sessões, envelopes, upload de documento, cadastro biométrico, cancelamentos
transactions:read Consultar transações, sessões, envelopes, status e download do documento
steps:write Listar, iniciar e concluir etapas de verificação
evidence:read Obter o pacote de evidências de uma transação
webhooks:write Cadastrar, listar, remover e testar webhooks
verification:write Verificação autenticada de documento

É a lista inteira. Não existem envelopes:read, envelopes:write, signing-sessions:read, signing-sessions:write, documents:write, templates:*, audit:read ou org:admin. Todos esses parecem plausíveis, e é exatamente por isso que aparecem em integrações — deduzidos da rota, não lidos da documentação.

O mapa: rota por rota

A tabela abaixo é o que evita a dedução errada. Repare quantas famílias de rota terminam em transactions:*:

Rota Escopo exigido
POST /v1/transactions transactions:write
GET /v1/transactions · GET /v1/transactions/{id} transactions:read
POST /v1/transactions/{id}/document · /presign · /confirm transactions:write
GET /v1/transactions/{id}/download transactions:read
GET /v1/transactions/{id}/evidence evidence:read
GET /v1/transactions/{id}/steps · /steps/{id}/start · /complete steps:write
POST /v1/signing-sessions · /cancel · /link transactions:write
GET /v1/signing-sessions · /{id}/status transactions:read
POST /v1/envelopes · /sessions · /combined-stamp · /cancel transactions:write
GET /v1/envelopes/{id} transactions:read
PUT · DELETE /v1/users/{id}/enrollment transactions:write
GET /v1/users/{id}/enrollment transactions:read
POST · GET · DELETE /v1/webhooks… webhooks:write
POST /v1/verify/document verification:write

As linhas em destaque são as que quebram a intuição. Um recurso pode ter caminho próprio, verbos próprios e documentação própria — e nenhum escopo próprio.

Duas leituras que também surpreendem

Listar webhooks exige webhooks:write. Não há webhooks:read: as quatro rotas de webhook ficam sob o mesmo escopo de escrita.

Listar etapas exige steps:write. GET /v1/transactions/{id}/steps é uma leitura sob escopo de escrita, porque as três rotas de etapa foram agrupadas por atividade: quem lista etapas está conduzindo o fluxo do signatário, não observando de fora. Se a sua integração só quer acompanhar o andamento, use GET /v1/transactions/{id} com transactions:read — mais barato em permissão e suficiente para monitorar.

Por que o desenho é assim

Vale entender a lógica, porque ela torna o mapa memorizável. Os escopos descrevem capacidades, não caminhos de URL. "Iniciar ou alterar um fluxo de assinatura" é uma capacidade — e criar uma transação, criar uma sessão, montar um envelope, subir um documento e cadastrar uma biometria são todos exercícios dela.

Isso tem uma consequência prática boa: quando a API ganha uma rota nova dentro de uma capacidade existente, as credenciais já emitidas continuam funcionando. Um escopo por recurso obrigaria a reemitir credenciais a cada endpoint novo, e integrações antigas quebrariam a cada evolução da API.

O custo desse desenho é justamente a surpresa que este artigo resolve: o nome do escopo não é dedutível do nome da rota. Por isso o mapa acima.

O erro que parece credencial errada

O pedido de token é assim:

POST https://api-hml.signdocs.com.br/oauth2/token Content-Type: application/x-www-form-urlencoded grant_type=client_credentials &client_id=... &client_secret=... &scope=transactions%3Awrite%20transactions%3Aread%20evidence%3Aread

Agora o caso ruim. Suponha uma integração que só cria envelopes e deduziu o escopo do nome da rota:

// Escopos deduzidos da rota — nenhum deles existe scope=envelopes:write envelopes:read // 400 Bad Request { "detail": "None of the requested scopes are authorized" }

Leia a mensagem com atenção: ela é verdadeira e enganosa ao mesmo tempo. Nenhum dos escopos pedidos está autorizado — porque nenhum deles existe. Mas o erro chega no passo da autenticação, fala em autorização e não menciona escopo inválido, então a investigação começa pelo client_id e pelo client_secret, que estão corretos. Quando a emissão de token falha com 400, confira a string de escopos antes de desconfiar da credencial.

Que a documentação também erre isso não é hipótese: guias nossos chegaram a publicar signing-sessions:read e signing-sessions:write em sete lugares, incluindo trechos de copiar e colar, e um README de SDK trazia um documents:write inexistente. Foi corrigido — mas se você copiou um snippet antigo, é um candidato imediato.

O caso pior: o escopo errado que não dá erro

O filtro do endpoint de token é por interseção, e ele só recusa quando o resultado é vazio. Isto passa sem reclamar:

scope=transactions:write envelopes:write // 200 — token emitido, com transactions:write apenas. // "envelopes:write" foi descartado em silêncio.

Um escopo digitado errado no meio de uma lista boa não gera erro nenhum na emissão. A falha aparece depois, como 403 na rota que dependia do escopo descartado — longe, no tempo e no código, da linha que a causou.

Daí a rotina de depuração: quando uma rota devolve 403 com credencial válida, não olhe primeiro para a rota. Decodifique o token emitido e compare a lista de escopos que ele efetivamente carrega com a que você pediu. A diferença entre as duas é o defeito.

Conjuntos que funcionam

// Sistema que envia documentos e acompanha o resultado scope=transactions:write transactions:read // O mesmo, arquivando o pacote de evidências scope=transactions:write transactions:read evidence:read // Painel de monitoramento — somente leitura scope=transactions:read // Integração que conduz as etapas do signatário scope=transactions:write transactions:read steps:write // Rotina de setup, executada uma vez scope=webhooks:write

Duas observações sobre o menor privilégio aqui. steps:write só é necessário se a sua integração conduz as etapas por conta própria — no fluxo hospedado, quem conduz é a página de assinatura, com outro token, o de embed, e o seu Bearer não participa. E webhooks:write pertence à rotina de configuração, não ao serviço que roda em produção: separar as duas credenciais custa pouco e evita que um token de aplicação possa reapontar os seus webhooks.

Roteiro de depuração

  1. 400 na emissão do token? Compare a string de escopos com a lista de seis. Um nome fora dela é a causa mais provável — não a credencial.
  2. 403 numa rota específica? Decodifique o token e verifique quais escopos ele realmente carrega. Um escopo inválido pedido junto de válidos é descartado em silêncio.
  3. Copiou de um exemplo antigo? Procure por signing-sessions:, envelopes: e documents: no seu código e na sua configuração. Nenhum dos três existe.
  4. 401, e não 403? Aí o problema é outro: credencial, token expirado (a vida útil é de 900 segundos) — ou uma rota que não aceita Bearer, tema do guia sobre os dois tokens da API.

Para o mecanismo de autenticação em si — client_credentials, formato do token, rotação de segredo —, veja OAuth2 e autenticação segura e como obter as credenciais. Para as demais armadilhas de primeira integração, os 12 erros mais comuns.

Perguntas Frequentes

Quais são os escopos da API?

São seis, e apenas seis: transactions:write, transactions:read, steps:write, evidence:read, webhooks:write e verification:write. Não existem envelopes:*, signing-sessions:*, documents:*, templates:*, audit:read nem org:admin. Qualquer nome fora dessa lista é um escopo inventado.

Por que /v1/envelopes usa transactions:write?

Porque os escopos foram desenhados em torno de capacidades, não de caminhos de URL. Criar um envelope, criar uma sessão de assinatura, cadastrar uma biometria e enviar um documento são todos "iniciar ou alterar um fluxo de assinatura" — a mesma capacidade, o mesmo escopo. Novas rotas passam a existir sem exigir um escopo novo em cada credencial já emitida.

O que significa o erro "None of the requested scopes are authorized"?

Que nenhum dos escopos que você pediu está entre os concedidos à sua credencial. O endpoint de token filtra o pedido contra a lista concedida e, quando a interseção é vazia, responde 400. Como o erro chega no passo da autenticação, ele parece problema de credencial — mas o client_id e o client_secret podem estar perfeitos. Confira primeiro a string de escopos.

E se eu pedir um escopo válido junto de um inválido?

O pedido passa. O filtro é por interseção, não exige que todos os escopos sejam reconhecidos: se ao menos um for concedido, o token sai — com os escopos válidos. Isso é conveniente e traiçoeiro, porque um escopo digitado errado no meio de uma lista boa não gera erro nenhum, e a falha só aparece depois, como 403 na rota que dependia dele.

Por que GET /v1/transactions/{id}/steps exige steps:write?

É a exceção que confunde: uma rota de leitura exigindo um escopo de escrita. As três rotas de etapas — listar, iniciar e concluir — ficam juntas sob steps:write, porque quem lista etapas na prática está conduzindo o fluxo do signatário. Uma integração que só monitora status deve usar GET /v1/transactions/{id} com transactions:read, não a listagem de etapas.

Como peço os escopos na requisição de token?

No corpo, em application/x-www-form-urlencoded, com grant_type=client_credentials e os escopos separados por espaço no campo scope. O token resultante expira em 900 segundos (15 minutos), então a integração precisa renová-lo — não guarde um token entre execuções longas.

Qual conjunto mínimo devo pedir?

Depende do que a integração faz. Só criar e acompanhar: transactions:write transactions:read. Acrescente evidence:read se for baixar o pacote de evidências, steps:write se conduzir as etapas você mesmo (não é necessário no fluxo hospedado), webhooks:write só no momento do cadastro dos webhooks e verification:write apenas se usar a verificação de documentos autenticada.

Comece com o token certo na primeira tentativa

Seis escopos, um mapa de rotas e o princípio do menor privilégio aplicado sem quebrar a integração. Crie credenciais de homologação gratuitas e teste a emissão do token em minutos.

Criar credenciais de homologação Fale com o time comercial