Entendendo os erros da API
Quando algo dá errado, a API responde com um array errors no corpo da resposta. Cada erro traz uma message e um objeto extensions, e é no extensions.code que está a informação que importa para decidir o que fazer.
Na maioria dos casos o status HTTP continua sendo 200, porque o erro é de execução da query, não do transporte. Só três situações fogem disso: requisição sem token autenticado (401), corpo com JSON inválido (400) e limite de requisições estourado (429).
Lendo a estrutura de um erro
{
"data": null,
"errors": [
{
"message": "Record not found",
"locations": [{ "line": 2, "column": 3 }],
"path": ["getPerson"],
"extensions": { "code": "NOT_FOUND" }
}
]
}
messageé um texto fixo por tipo de erro. Não tente interpretá-lo, porque ele pode mudar de redação.pathaponta qual query ou mutation da sua requisição falhou. Numa requisição com várias operações, é como você sabe qual delas caiu.extensions.codeé o contrato estável. É por ele que você decide o tratamento.datapode vir preenchido junto com oerrorsquando parte da requisição funcionou e parte falhou.
Conhecendo os códigos de erro
| Código | Quando acontece | O que fazer |
|---|---|---|
FORBIDDEN |
O usuário do token não tem permissão para a operação. | Pare e reporte. Repetir a chamada devolve o mesmo erro. |
UNPROCESSABLE |
Os dados enviados não passaram nas validações. | Corrija os valores apontados e repita. |
NOT_FOUND |
O registro informado não existe. | Confira o id. Não é falta de permissão. |
PLAN_EXCEEDED |
O plano do licenciado não libera o recurso. | Pare e reporte. Não é falta de permissão. |
RATE_LIMITED |
Você estourou o limite de requisições. | Aguarde o tempo indicado e repita. Veja Limite de requisições da API. |
sem code |
O valor de uma variável não é aceito pelo tipo. | Corrija o valor. A própria resposta lista os valores válidos. |
Tratando o FORBIDDEN
O FORBIDDEN é um erro de autorização, não de autenticação: seu token é válido, mas o usuário por trás dele não tem acesso àquela operação.
Quando a negativa vem de uma permissão de acesso, a resposta também diz por quê, em duas chaves que aparecem só quando são elas que barram o acesso:
extensions.required_scope
O nome técnico da permissão que está bloqueada para esse usuário. É o mesmo nome que você encontra no AccessScopeEnum do schema, com a descrição em português.
extensions.required_apps
A lista dos apps que a permissão exige, quando nenhum deles está instalado. Basta um estar ativo para liberar.
Exemplo de permissão bloqueada para o usuário:
{
"data": null,
"errors": [
{
"message": "The user does not have permission to perform this action",
"path": ["getPerson"],
"extensions": { "code": "FORBIDDEN", "required_scope": "person_show" }
}
]
}
Aqui person_show é a permissão Pessoa → Consultar, na tela de Permissão de Acesso. Para resolver, alguém com acesso às configurações do sistema precisa liberar esse item para o usuário.
Exemplo de app obrigatório não instalado:
{
"data": null,
"errors": [
{
"message": "The user does not have permission to perform this action",
"path": ["getMdfes"],
"extensions": {
"code": "FORBIDDEN",
"required_apps": ["mdfe", "mdfe_acbr"]
}
}
]
}
A permissão está liberada para o usuário, mas o recurso depende de um app que o licenciado não tem instalado. Nesse caso liberar permissão não resolve: é preciso instalar um dos apps pela Loja de Apps.
As duas chaves são causas independentes, então podem vir juntas. Quando isso acontece, resolver só uma das duas não libera o acesso:
{
"data": null,
"errors": [
{
"message": "The user does not have permission to perform this action",
"path": ["getMdfes"],
"extensions": {
"code": "FORBIDDEN",
"required_scope": "mdfe_list",
"required_apps": ["mdfe", "mdfe_acbr"]
}
}
]
}
Quando a negativa não vem de uma permissão de acesso (por exemplo, uma operação restrita a administradores), o erro traz só o código:
{
"extensions": { "code": "FORBIDDEN" }
}
Não tente prever a permissão antes de chamar
O campo denied_access_scopes do usuário lista as permissões que estão bloqueadas para ele, e é tentador usá-lo para calcular por eliminação o que está liberado, comparando com a lista completa de permissões do schema. Não faça isso: a conta parece certa e erra em silêncio, porque uma permissão liberada ainda pode estar barrada por app não instalado, e porque o resultado envelhece no instante em que alguém muda a configuração.
O caminho confiável é o contrário: faça a chamada. Se o acesso não existe, a resposta é um FORBIDDEN que nomeia a causa, e isso já é suficiente para você saber que não deve seguir por ali e o que precisa reportar.
Tratando o UNPROCESSABLE
É o erro de dados. A message é sempre Unprocessable entity, e o que interessa está em extensions.errors: um objeto com uma entrada por atributo com problema, cada uma com a lista de mensagens em português.
{
"data": { "createProduct": null },
"errors": [
{
"message": "Unprocessable entity",
"path": ["createProduct"],
"extensions": {
"code": "UNPROCESSABLE",
"errors": {
"gross_weight": ["não pode ficar em branco"]
}
}
}
]
}
Quando o problema não é de um atributo específico, mas do registro como um todo, ele vem num atributo chamado base:
{
"extensions": {
"code": "UNPROCESSABLE",
"errors": {
"base": ["Não é possível excluir o registro pois existem vendas dependentes"]
}
}
}
E quando o erro está num item de uma coleção (os produtos de uma venda, por exemplo), a lista de erros segue a mesma ordem dos itens que você enviou, com um objeto vazio para os itens sem problema. No exemplo abaixo, o primeiro produto da venda é que está sem estoque:
{
"extensions": {
"code": "UNPROCESSABLE",
"errors": {
"invoice_products_attributes": [
{ "quantity": ["indisponível em estoque"] },
{}
]
}
}
}
Use a posição para saber de qual item é o erro. Ela é a única ligação entre o erro e o item que você enviou.
Tratando valores fora do enum
Vários campos da API aceitam apenas um conjunto fechado de valores. Quando você envia um valor fora desse conjunto, a requisição é recusada antes de chegar ao sistema, e o erro não tem extensions.code: ele vem no formato nativo do GraphQL, com extensions.value e extensions.problems.
{
"errors": [
{
"message": "Variable $attributes of type UserPermissionsInput was provided invalid value for denied_access_scopes.0 (Expected \"venda_alterar\" to be one of: activity_access, activity_create, ...)",
"extensions": {
"value": { "denied_access_scopes": ["venda_alterar"] },
"problems": [
{
"path": ["denied_access_scopes", 0],
"explanation": "Expected \"venda_alterar\" to be one of: activity_access, activity_create, ..."
}
]
}
}
]
}
Esse erro é o mais fácil de resolver, porque a própria resposta lista os valores aceitos. O path dentro de problems aponta o campo e, em listas, a posição do valor recusado.
Atenção: um valor aceito pelo enum ainda pode ser recusado depois, por regra de negócio, e aí você recebe um UNPROCESSABLE. É o que acontece ao bloquear uma permissão de que outra permissão liberada depende: o valor existe, mas a combinação não é válida.
Tratando o NOT_FOUND
A message é sempre Record not found, sem dizer o tipo do registro, porque o path do erro já identifica a operação que falhou.
Vale reforçar a diferença: NOT_FOUND é registro inexistente, e FORBIDDEN é registro que você não pode ver. A API não devolve NOT_FOUND para esconder um registro que existe.
Tratando o PLAN_EXCEEDED
Acontece quando a operação esbarra num limite do plano do licenciado, e não numa permissão de usuário. A message já vem pronta para exibição, nomeando o recurso e o plano, e o extensions.landing_page_url traz a página com os detalhes do recurso (pode vir null).
{
"data": { "createApp": null },
"errors": [
{
"message": "A funcionalidade \"plataformas de vendas\" não está disponível para o plano Plano Grátis",
"path": ["createApp"],
"extensions": {
"code": "PLAN_EXCEEDED",
"landing_page_url": "https://facil123.com.br/planos"
}
}
]
}
Liberar permissões não resolve esse erro. A saída é mudar de plano.
Tratando as respostas que não são GraphQL
Três situações são recusadas antes da execução da query, e nelas o status HTTP é que carrega a informação:
401 Unauthorized
Token ausente, inválido ou revogado. Se você não enviar o header Accept: application/json, a resposta vira um redirecionamento (302) para a tela de login em vez de um 401. Veja Características do token de API.
400 Bad Request
O corpo da requisição não é um JSON válido. A resposta traz um errors com a mensagem começando em Invalid JSON in request body.
429 Too Many Requests
Limite de requisições estourado. A resposta traz o cabeçalho Retry-After com quantos segundos esperar. Veja Limite de requisições da API.
Perguntas e respostas
1 - Recebi um FORBIDDEN sem required_scope nem required_apps. O que faço?
R: Essa negativa não vem de uma permissão de acesso configurável, e sim de uma restrição da própria operação (as restritas a administradores, por exemplo). Não há item para liberar na tela de permissões.
2 - O status HTTP foi 200 mas veio um errors. A requisição funcionou ou não?
R: Não funcionou. Em GraphQL o status 200 diz apenas que a requisição chegou e foi processada. Sempre verifique a presença do array errors, não só o status.
3 - Posso repetir automaticamente uma chamada que falhou?
R: Só nos casos de RATE_LIMITED, respeitando o Retry-After. FORBIDDEN, PLAN_EXCEEDED, NOT_FOUND e UNPROCESSABLE devolvem o mesmo resultado em toda tentativa, e repetir só consome sua cota de requisições.