Errores
Todo error de la API sale en el mismo formato, en cualquier ruta y en cualquier estado.
{ "error": { "type": "authentication_error", "code": "api_key_invalid", "message": "Clave de API inválida, revocada o vencida.", "request_id": "0a8c1f2e-3b4d-4c5f-9a6b-7c8d9e0f1a2b", "doc_url": "https://docs.fatureihoje.com/es/errors#api_key_invalid" }}Los campos
Sección titulada «Los campos»| Campo | Siempre viene | Qué es |
|---|---|---|
type | Sí | La familia del error. Sirve para ramificar por rango, sin conocer cada código |
code | Sí | El caso exacto. Es con él que su programa decide qué hacer |
message | Sí | Frase para que una persona lea. Sale en el idioma de Accept-Language |
doc_url | Sí | La dirección de esta página, en el ancla del código |
request_id | En /public/v1 | Identificador de esta llamada. Es lo que pide el soporte |
param | No | Reservado. El contrato lo prevé para el error de un solo campo, y ninguna ruta lo emite hoy |
errors | No | Lista de campos inválidos. Solo en el 422 de validación |
code es contrato, message no
Sección titulada «code es contrato, message no»El code es estable y está escrito en inglés. No cambia de valor, no cambia de nombre y no cambia con el idioma. Un código publicado en el catálogo sigue existiendo aunque la API deje de emitirlo, porque una integración que lo trata no puede romperse.
La message es lo contrario: es texto para que una persona lo lea y sale traducido según el encabezado Accept-Language de la solicitud (pt-BR es el valor por defecto, y en y es se aceptan).
El par código y estado es fijo
Sección titulada «El par código y estado es fijo»Cada code tiene un único estado HTTP, y nunca varía. validation_failed es siempre 422; rate_limit_exceeded es siempre 429. Por eso el catálogo de abajo publica los dos juntos: si usted trata el código, el estado es consecuencia, y nunca va a llegar un par distinto del que está aquí.
Las siete familias de type:
type | Rango | Cuándo |
|---|---|---|
invalid_request_error | 400, 404, 413, 415 | La solicitud está equivocada, o lo que pide no existe |
authentication_error | 401 | La clave no fue aceptada |
permission_error | 403 | La clave fue aceptada, pero esta acción no está permitida |
conflict_error | 409 | Conflicto con el estado actual |
validation_error | 422 | Campo inválido |
rate_limit_error | 429 | Presupuesto de llamadas agotado |
api_error | 500, 503 | El problema es nuestro |
errors, en el 422
Sección titulada «errors, en el 422»Cuando uno o más campos no son válidos, el cuerpo trae la lista, campo por campo:
{ "error": { "type": "validation_error", "code": "validation_failed", "message": "Uno o más campos no son válidos.", "errors": [ { "param": "email", "code": "invalid", "message": "Correo inválido" }, { "param": "phone", "code": "invalid", "message": "Teléfono inválido" } ], "request_id": "6f12b0d4-8e33-4a91-b2c7-5d0a7e93f118", "doc_url": "https://docs.fatureihoje.com/es/errors#validation_failed" }}param es el nombre del campo tal como la API lo recibe. message es texto para una persona; lo que su programa usa es param.
Ese param es el de adentro de errors, y es el único que verá hoy. El param del nivel de arriba, al lado de code, está previsto en el contrato pero ninguna ruta lo emite: lea siempre la lista errors.
request_id
Sección titulada «request_id»Toda respuesta de /public/v1, con error o sin error, trae el encabezado Request-Id. En el cuerpo de error, el mismo valor aparece en request_id.
Guarde ese valor en su registro siempre que una llamada falle. Es por él que el soporte encuentra la llamada, y es el único camino cuando el error es interno.
Una ruta fuera de /public/v1 responde 404 antes de que la llamada reciba un identificador, y por eso ese 404 sale sin request_id. Si recibió un error sin request_id, revise primero la ruta.
doc_url
Sección titulada «doc_url»El doc_url apunta a esta página, ya en el ancla del código:
https://docs.fatureihoje.com/es/errors#api_key_invalidEn portugués la dirección va sin prefijo de idioma: docs.fatureihoje.com/errors#.... Puede registrar esa dirección en su log de error: es corta, permanente, y lleva directo a la explicación del código.
Leer el error en su código
Sección titulada «Leer el error en su código»El ejemplo llama a GET /public/v1/me y trata las dos salidas: la respuesta buena y el cuerpo de error. Sirve para cualquier ruta, porque el formato del error es el mismo en todas.
curl -sS -i https://api.fatureihoje.com/public/v1/me \ -H "Authorization: Bearer $FH_API_KEY" \ -H "Accept-Language: es"El -i imprime los encabezados antes del cuerpo, así que el Request-Id aparece junto al error.
const res = await fetch('https://api.fatureihoje.com/public/v1/me', { headers: { Authorization: `Bearer ${process.env.FH_API_KEY}`, 'Accept-Language': 'es', },});
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);Ejecute con node error.mjs. Node.js 18 o más nuevo.
<?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: es', ], 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 devuelve false cuando la conexión ni siquiera ocurrió, sin lanzar nada.if ($raw === false) { fwrite(STDERR, 'falla de red: ' . 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;Ejecute con php error.php. Necesita las extensiones curl y json. Sin curl_close: desde PHP 8 el recurso se libera solo, y en 8.5 la función quedó 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": "es", },)
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"])Ejecute con python3 error.py. Solo biblioteca estándar.
Catálogo
Sección titulada «Catálogo»Esta es la lista completa. Se genera del contrato de la API al construir el sitio, así que un código nuevo aparece aquí solo y ningún código queda afuera.
| 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 |
Qué hacer en cada uno
Sección titulada «Qué hacer en cada uno»-
api_key_invalid -
Qué hacer Revise el encabezado
Authorization: Bearer <clave>, que la clave siga activa en el panel y que la IP de salida de su servidor esté en la lista de la clave. La respuesta es la misma en todos esos casos a propósito: no dice cuál fue el motivo.Lea también Autenticación
-
access_denied -
Qué hacer Acceso denegado sin causa conocida. Este código sale cuando el motivo no es el permiso de la clave ni el del miembro. Guarde el
request_idy hable con el soporte. -
permission_missing -
Qué hacer Vea los permisos de la clave en
key.permissions, en la respuesta deGET /public/v1/me, y compárelos con lo que exige la ruta en la Referencia. El permiso de una clave no cambia después de creada, ni en la rotación: para cambiarlo, cree otra clave.Lea también Permisos
-
member_permission_denied -
Qué hacer La clave tiene el permiso, pero el miembro que representa no puede hacer esto en el panel. Ajuste el rol o los permisos de ese miembro, o use una clave de otro miembro.
Lea también Permisos
-
member_suspended -
Qué hacer Reservado. Hoy el miembro suspendido cae en el 401 de
api_key_invalid, porque la respuesta de la autenticación no puede confirmar que la clave es válida. Si este código aparece, reactive al miembro en el panel. -
organization_suspended -
Qué hacer Reservado, por el mismo motivo que el anterior, pero para la empresa. Si aparece, resuelva la situación de la empresa en el panel.
-
subscription_inactive -
Qué hacer Regularice el pago en el panel. Repetir la llamada antes de eso devuelve el mismo error, así que insistir en bucle no sirve.
-
plan_limit_reached -
Qué hacer El más común es la cantidad de claves activas. Vea el uso en el panel, revoque lo que ya no usa o suba de plan.
Lea también Límites de uso
-
resource_not_found -
Qué hacer El id no existe o es de otra empresa. La API responde 404 en los dos casos, nunca 403: un 403 contaría que el registro existe en otro lugar. Revise el id y la empresa de la clave en
GET /public/v1/me. -
route_not_found -
Qué hacer La ruta no existe. Revise el método, la ruta y la dirección:
api.fatureihoje.comsirve solo/public/v1. Una ruta de un área que todavía no se publicó responde igual. -
invalid_request -
Qué hacer La solicitud está malformada, casi siempre JSON roto o un parámetro de consulta inválido. Corrija antes de repetir: la misma solicitud devuelve el mismo error.
-
validation_failed -
Qué hacer El cuerpo trae
errors, conparam,codeymessagede cada campo. Corrija lo que señala y envíe de nuevo: la misma solicitud devuelve el mismo error. -
conflict -
Qué hacer Lea el recurso otra vez y decida a partir de lo que dice ahora. Repetir sin releer devuelve el mismo conflicto. Es lo que responde, por ejemplo, un cambio de situación de cita que la agenda no permite (cancelada no vuelve, completada solo vuelve a confirmada), la edición de una orden de servicio ya completada o cancelada, la edición de un presupuesto aprobado o cancelado, la eliminación de un presupuesto que ya se convirtió en venta activa la segunda conversión del mismo presupuesto en venta o en orden, iniciar, completar o cancelar una orden ya completada o cancelada, cualquier escritura en un presupuesto absorbido en una unión, el código interno (
sku) que ya existe en la empresa, el nombre de variación repetido en el mismo producto, la eliminación de una categoría que todavía tiene productos o servicios, y en la venta: cualquier escritura en una venta cancelada, versión nueva o cancelación con factura activa, versión nueva con la comisión del vendedor ya pagada, cobrar o reprogramar una venta de contrato, anular un cobro ya anulado o con factura activa, y reprogramar sin cuota abierta. En finanzas: cambiar un campo protegido de un movimiento de venta o del banco, eliminar un movimiento de venta o del banco, marcar como pagado un movimiento cancelado o en el resumen, eliminar una cuenta que tiene movimientos, y la eliminación de serie que borraría más de lo pedido (conparamscope). -
idempotency_key_reused -
Qué hacer Genere un valor nuevo para cada operación distinta. Repetir la clave solo vale para repetir la MISMA solicitud, byte a byte.
Lea también Idempotencia
-
idempotency_in_progress -
Qué hacer Espere unos segundos y repita la misma solicitud, con la misma clave. No genere una clave nueva: eso ejecutaría la operación dos veces.
Lea también Idempotencia
-
unsupported_media_type -
Qué hacer Formulario, multipart y texto plano no se aceptan. Revise el
Content-Typeque su biblioteca envía por defecto. -
request_too_large -
Qué hacer Divida el envío en partes más pequeñas. El tope vale para toda llamada, con o sin
Idempotency-Key. -
rate_limit_exceeded -
Qué hacer Espere lo que dice el encabezado
Retry-Aftery retroceda progresivamente. No repita en un bucle apretado: el presupuesto es de la empresa y usted atrasa sus llamadas buenas.Lea también Límites de uso
-
request_failed -
Qué hacer La API no procesó la solicitud y el motivo no tiene código propio en el catálogo. Guarde el
request_id. Si el error se repite con la misma solicitud, envíe elrequest_idal soporte. -
internal_error -
Qué hacer El problema es de nuestro lado. La respuesta nunca trae detalle interno, a propósito. Puede intentar de nuevo después de esperar. Si persiste, envíe el
request_idadev@fatureihoje.com.
Próximo paso
Sección titulada «Próximo paso»- Límites de uso: el 429 y los encabezados que dicen cuánto queda.
- Idempotencia: los dos errores que aparecen cuando repite una solicitud.
- Autenticación: la guía del 401, que es el error más común al principio.