Erros
Todo erro da API sai no mesmo formato, em qualquer rota e em qualquer status.
{ "error": { "type": "authentication_error", "code": "api_key_invalid", "message": "Chave de API inválida, revogada ou expirada.", "request_id": "0a8c1f2e-3b4d-4c5f-9a6b-7c8d9e0f1a2b", "doc_url": "https://docs.fatureihoje.com/errors#api_key_invalid" }}Os campos
Seção intitulada “Os campos”| Campo | Sempre vem | O que é |
|---|---|---|
type | Sim | A família do erro. Serve para você ramificar por faixa, sem conhecer cada código |
code | Sim | O caso exato. É por ele que o seu programa decide o que fazer |
message | Sim | Frase para pessoa ler. Sai no idioma de Accept-Language |
doc_url | Sim | Endereço desta página, na âncora do código |
request_id | Em /public/v1 | Identificador desta chamada. É o que o suporte pede |
param | Não | Reservado. O contrato o prevê para o erro de um campo só, e nenhuma rota o emite hoje |
errors | Não | Lista de campos inválidos. Só no 422 de validação |
code é contrato, message não é
Seção intitulada “code é contrato, message não é”O code é estável e escrito em inglês. Ele não muda de valor, não muda de nome e não muda com o idioma. Um código publicado no catálogo continua existindo mesmo quando a API para de emiti-lo, porque integração que o trata não pode quebrar.
A message é o contrário: é texto para uma pessoa ler e sai traduzida conforme o cabeçalho Accept-Language da requisição (pt-BR é o padrão, e en e es são aceitos).
O par código e status é fixo
Seção intitulada “O par código e status é fixo”Cada code tem um único status HTTP, e ele nunca varia. validation_failed é sempre 422; rate_limit_exceeded é sempre 429. É por isso que o catálogo abaixo publica os dois juntos: se você trata o código, o status é consequência, e nunca vai chegar um par diferente do que está aqui.
As sete famílias de type:
type | Faixa | Quando |
|---|---|---|
invalid_request_error | 400, 404, 413, 415 | A requisição está errada, ou o que ela pede não existe |
authentication_error | 401 | A chave não foi aceita |
permission_error | 403 | A chave foi aceita, mas esta ação não é permitida |
conflict_error | 409 | Conflito com o estado atual |
validation_error | 422 | Campo inválido |
rate_limit_error | 429 | Orçamento de chamadas esgotado |
api_error | 500, 503 | O problema é nosso |
errors, no 422
Seção intitulada “errors, no 422”Quando um ou mais campos são inválidos, o corpo traz a lista, campo a campo:
{ "error": { "type": "validation_error", "code": "validation_failed", "message": "Um ou mais campos são inválidos.", "errors": [ { "param": "email", "code": "invalid", "message": "E-mail inválido" }, { "param": "phone", "code": "invalid", "message": "Telefone inválido" } ], "request_id": "6f12b0d4-8e33-4a91-b2c7-5d0a7e93f118", "doc_url": "https://docs.fatureihoje.com/errors#validation_failed" }}param é o nome do campo como a API o recebe. message é texto para pessoa; o que o seu programa usa é param.
Esse param é o de dentro de errors, e é o único que você vai ver hoje. O param do nível de cima, ao lado de code, está previsto no contrato mas nenhuma rota o emite: leia sempre a lista errors.
request_id
Seção intitulada “request_id”Toda resposta de /public/v1, com erro ou sem erro, traz o cabeçalho Request-Id. No corpo de erro, o mesmo valor aparece em request_id.
Guarde esse valor no seu registro sempre que uma chamada falhar. É por ele que o suporte acha a chamada, e é o único caminho quando o erro é interno.
Caminho fora de /public/v1 responde 404 antes de a chamada ganhar um identificador, e por isso esse 404 sai sem request_id. Se você recebeu um erro sem request_id, confira primeiro o caminho.
doc_url
Seção intitulada “doc_url”O doc_url aponta para esta página, já na âncora do código:
https://docs.fatureihoje.com/errors#api_key_invalidEm inglês e em espanhol o endereço leva o idioma: docs.fatureihoje.com/en/errors#... e docs.fatureihoje.com/es/errors#.... Pode registrar esse endereço no seu log de erro: ele é curto, permanente, e leva direto à explicação do código.
Ler o erro no seu código
Seção intitulada “Ler o erro no seu código”O exemplo chama GET /public/v1/me e trata as duas saídas: a resposta boa e o corpo de erro. Vale para qualquer rota, porque o formato do erro é o mesmo em todas.
curl -sS -i https://api.fatureihoje.com/public/v1/me \ -H "Authorization: Bearer $FH_API_KEY" \ -H "Accept-Language: pt-BR"O -i imprime os cabeçalhos antes do corpo, então o Request-Id aparece junto do erro.
const res = await fetch('https://api.fatureihoje.com/public/v1/me', { headers: { Authorization: `Bearer ${process.env.FH_API_KEY}`, 'Accept-Language': 'pt-BR', },});
const body = await res.json();
if (!res.ok) { const { code, type, message, request_id } = body.error; console.error(`${res.status} ${type} ${code}: ${message}`); console.error('request_id', request_id ?? res.headers.get('Request-Id')); if (code === 'rate_limit_exceeded') { console.error('esperar', res.headers.get('Retry-After'), 'segundos'); } process.exit(1);}
console.log(body.organization.name);Rode com node erro.mjs. Node.js 18 ou mais novo.
<?php
$headers = [];$ch = curl_init('https://api.fatureihoje.com/public/v1/me');curl_setopt_array($ch, [ CURLOPT_RETURNTRANSFER => true, CURLOPT_HTTPHEADER => [ 'Authorization: Bearer ' . getenv('FH_API_KEY'), 'Accept-Language: pt-BR', ], CURLOPT_HEADERFUNCTION => function ($ch, $line) use (&$headers) { $parts = explode(':', $line, 2); if (count($parts) === 2) { $headers[strtolower(trim($parts[0]))] = trim($parts[1]); } return strlen($line); },]);
$raw = curl_exec($ch);$status = curl_getinfo($ch, CURLINFO_HTTP_CODE);
// curl_exec devolve false quando a conexão nem aconteceu, sem lançar nada.if ($raw === false) { fwrite(STDERR, 'falha de rede: ' . curl_error($ch) . PHP_EOL); exit(1);}
$body = json_decode($raw, true);
if ($status >= 400) { $error = $body['error']; fwrite(STDERR, $status . ' ' . $error['type'] . ' ' . $error['code'] . ': ' . $error['message'] . PHP_EOL); fwrite(STDERR, 'request_id ' . ($error['request_id'] ?? $headers['request-id'] ?? '') . PHP_EOL); if ($error['code'] === 'rate_limit_exceeded') { fwrite(STDERR, 'esperar ' . ($headers['retry-after'] ?? '') . ' segundos' . PHP_EOL); } exit(1);}
echo $body['organization']['name'], PHP_EOL;Rode com php erro.php. Precisa das extensões curl e json. Sem curl_close: desde o PHP 8 o recurso é liberado sozinho, e no 8.5 a função ficou obsoleta.
import jsonimport osimport sysimport urllib.errorimport urllib.request
request = urllib.request.Request( "https://api.fatureihoje.com/public/v1/me", headers={ "Authorization": f"Bearer {os.environ['FH_API_KEY']}", "Accept-Language": "pt-BR", },)
try: with urllib.request.urlopen(request) as response: body = json.load(response)except urllib.error.HTTPError as failure: error = json.load(failure)["error"] print(f"{failure.code} {error['type']} {error['code']}: {error['message']}", file=sys.stderr) request_id = error.get("request_id") or failure.headers.get("Request-Id") print("request_id", request_id, file=sys.stderr) if error["code"] == "rate_limit_exceeded": print("esperar", failure.headers.get("Retry-After"), "segundos", file=sys.stderr) raise SystemExit(1)
print(body["organization"]["name"])Rode com python3 erro.py. Só biblioteca padrão.
Catálogo
Seção intitulada “Catálogo”Esta é a lista inteira. Ela é gerada do contrato da API no build do site, então código novo aparece aqui sozinho e nenhum código fica de fora.
| Código | HTTP | Tipo |
|---|---|---|
api_key_invalid | 401 | authentication_error |
access_denied | 403 | permission_error |
permission_missing | 403 | permission_error |
member_permission_denied | 403 | permission_error |
member_suspended | 403 | permission_error |
organization_suspended | 403 | permission_error |
subscription_inactive | 403 | permission_error |
plan_feature_unavailable | 403 | permission_error |
plan_limit_reached | 403 | permission_error |
resource_not_found | 404 | invalid_request_error |
route_not_found | 404 | invalid_request_error |
invalid_request | 400 | invalid_request_error |
validation_failed | 422 | validation_error |
conflict | 409 | conflict_error |
idempotency_key_reused | 422 | validation_error |
idempotency_in_progress | 409 | conflict_error |
unsupported_media_type | 415 | invalid_request_error |
request_too_large | 413 | invalid_request_error |
rate_limit_exceeded | 429 | rate_limit_error |
request_failed | 400 | invalid_request_error |
internal_error | 500 | api_error |
service_unavailable | 503 | api_error |
O que fazer em cada um
Seção intitulada “O que fazer em cada um”-
api_key_invalid -
O que fazer Confira o cabeçalho
Authorization: Bearer <chave>, se a chave continua ativa no painel e se o IP de saída do seu servidor está na lista da chave. A resposta é a mesma em todos esses casos, de propósito: ela não diz qual foi o motivo.Leia também Autenticação
-
access_denied -
O que fazer Acesso negado sem causa conhecida. Este código sai quando o motivo não é a permissão da chave nem a do membro. Guarde o
request_ide fale com o suporte. -
permission_missing -
O que fazer Veja o que a chave tem em
key.permissions, na resposta deGET /public/v1/me, e compare com o que a rota exige na Referência. A permissão de uma chave não muda depois de criada, nem na rotação: para mudar, crie outra chave.Leia também Permissões
-
member_permission_denied -
O que fazer A chave tem a permissão, mas o membro que ela representa não pode fazer isso no painel. Ajuste o papel ou as permissões desse membro, ou use uma chave de outro membro.
Leia também Permissões
-
member_suspended -
O que fazer Reservado. Hoje o membro suspenso cai no 401 de
api_key_invalid, porque a resposta da autenticação não pode dizer que a chave é válida. Se este código aparecer, reative o membro no painel. -
organization_suspended -
O que fazer Reservado, pelo mesmo motivo do anterior, só que para a empresa. Se aparecer, resolva a situação da empresa no painel.
-
subscription_inactive -
O que fazer Regularize o pagamento no painel. Repetir a chamada antes disso devolve o mesmo erro, então não vale a pena tentar de novo em laço.
-
plan_limit_reached -
O que fazer O mais comum é o número de chaves ativas. Veja o uso no painel, revogue o que não usa mais ou suba de plano.
Leia também Limites de uso
-
resource_not_found -
O que fazer O id não existe ou é de outra empresa. A API responde 404 nos dois casos, nunca 403: dizer 403 contaria que o registro existe em outro lugar. Confira o id e a empresa da chave em
GET /public/v1/me. -
route_not_found -
O que fazer O caminho não existe. Confira o método, o caminho e o endereço:
api.fatureihoje.comserve só/public/v1. Rota de área que ainda não foi publicada também responde assim. -
invalid_request -
O que fazer A requisição está malformada, quase sempre JSON quebrado ou parâmetro de consulta inválido. Corrija antes de repetir: repetir igual devolve o mesmo erro.
-
validation_failed -
O que fazer O corpo traz
errors, comparam,codeemessagede cada campo. Corrija o que ele aponta e reenvie: a mesma requisição devolve o mesmo erro. -
conflict -
O que fazer Leia o recurso de novo e decida a partir do que ele diz agora. Repetir sem reler devolve o mesmo conflito. É o que responde, por exemplo, a mudança de situação de compromisso que a agenda não permite (cancelado não volta, concluído só volta para confirmado), a edição de ordem de serviço já concluída ou cancelada, a edição de orçamento aprovado ou cancelado, a exclusão de orçamento que já virou venda ativa a segunda conversão do mesmo orçamento em venda ou em OS, iniciar, concluir ou cancelar OS já concluída ou cancelada, qualquer escrita em orçamento absorvido numa junção, o código interno (
sku) que já existe na empresa, o nome de variação repetido no mesmo produto, a exclusão de categoria que ainda tem produto ou serviço, e na venda: qualquer escrita em venda cancelada, versão nova ou cancelamento com nota fiscal ativa, versão nova com a comissão do vendedor já paga, receber ou reagendar venda de contrato, estornar recebimento já estornado ou com nota ativa, e reagendar sem parcela em aberto. No financeiro: mudar campo protegido de lançamento de venda ou do banco, excluir lançamento de venda ou do banco, marcar como pago lançamento cancelado ou na fatura, excluir conta que tem lançamento, e a exclusão de série que apagaria mais do que o pedido (comparamscope). -
idempotency_key_reused -
O que fazer Gere um valor novo para cada operação diferente. Repetir a chave só vale para repetir a MESMA requisição, byte a byte.
Leia também Idempotência
-
idempotency_in_progress -
O que fazer Espere alguns segundos e repita a mesma requisição, com a mesma chave. Não gere chave nova: isso executaria a operação duas vezes.
Leia também Idempotência
-
unsupported_media_type -
O que fazer Formulário, multipart e texto puro não são aceitos. Confira o
Content-Typeque a sua biblioteca manda por padrão. -
request_too_large -
O que fazer Quebre o envio em partes menores. O teto vale para toda chamada, com ou sem
Idempotency-Key. -
rate_limit_exceeded -
O que fazer Espere o que o cabeçalho
Retry-Afterdiz e recue progressivamente. Não repita em laço apertado: o orçamento é da empresa e você atrasa as chamadas boas dela.Leia também Limites de uso
-
request_failed -
O que fazer A API não processou a requisição e o motivo não tem código próprio no catálogo. Guarde o
request_id. Se o erro se repetir com a mesma requisição, mande orequest_idpara o suporte. -
internal_error -
O que fazer O problema é do nosso lado, e a resposta nunca traz detalhe interno, de propósito. Pode tentar de novo depois de esperar. Se persistir, mande o
request_idparadev@fatureihoje.com.
Próximo passo
Seção intitulada “Próximo passo”- Limites de uso: o 429 e os cabeçalhos que dizem quanto sobrou.
- Idempotência: os dois erros que aparecem quando você repete uma requisição.
- Autenticação: o roteiro do 401, que é o erro mais comum no começo.