Comenzar
Errores y códigos de estado
Cómo informa errores la API REST de Rhombus: códigos de estado HTTP, el cuerpo de error y su errorCode, errores de autenticación y de permisos, el campo error en las respuestas 200, errores de validación y de JSON, errores del servidor y qué fallos reintentar.
En esta página
Esta página se tradujo automáticamente. Si encuentras errores o tienes sugerencias, escríbenos.
La API REST informa una solicitud fallida de una de dos formas, así que revisa ambas:
- Un código de estado HTTP de error cuando la solicitud no se puede procesar: JSON mal formado, un campo que falta, un problema con la credencial, un permiso denegado o un límite de tasa. El cuerpo tiene
"error": truey unerrorCodelegible por máquina. - HTTP
200con"error": truecuando la solicitud era válida pero la acción falló o se rechazó, por ejemplo un rol que no puedes asignar. El motivo está enerrorMsg:
{
"error": true,
"errorMsg": "User not permitted to generate an api token for the specified role"
}Una solicitud tuvo éxito solo cuando el estado es 2xx y error es false.
El sobre de la respuesta
Cada respuesta JSON de la API REST incluye los mismos tres campos junto a sus datos:
| Campo | Presente | Significado |
|---|---|---|
error | Siempre | false si tuvo éxito, true si la acción falló |
errorMsg | Cuando error es true | Un motivo legible. Regístralo y muéstralo a un operador; no lo analices, porque su texto puede cambiar |
warningMsg | Rara vez | La solicitud tuvo éxito, pero algo requiere atención, como un valor predeterminado que el servidor aplicó por ti |
Las respuestas de error tienen el mismo cuerpo en todos los hosts: la API REST, los hosts de medios, los handshakes de WebSocket y el servidor MCP.
Cada respuesta de error incluye errorCode, un código estable para decidir qué hacer, junto a errorMsg, un mensaje para mostrar o registrar. Los códigos nunca se renombran ni se eliminan, pero se agregan nuevos: maneja un código que no reconozcas según su estado HTTP.
Toda respuesta con un estado de error tiene el mismo cuerpo. msg es la frase de motivo HTTP, que se conserva para clientes antiguos; status repite el estado HTTP; timestamp es el momento del error, en milisegundos de época Unix; txid es el ID de la solicitud que debes indicar al soporte, también enviado como encabezado x-rhombus-txid:
{
"msg": "Forbidden",
"error": true,
"errorCode": "FUNCTIONALITY_REQUIRED",
"errorMsg": "Your role is missing a functionality this action requires.",
"status": 403,
"timestamp": 1791367200000,
"txid": "yJ2kQw8LTaO1xX4hVd7mPA"
}Los códigos que devuelve la API hoy (los significados están en inglés):
| Código | Estado | Significado |
|---|---|---|
BAD_REQUEST | 400 | The request is invalid. |
VALIDATION_FAILED | 400 | The request failed validation. |
MISSING_REQUIRED_FIELD | 400 | The request is missing a required field. |
INVALID_JSON | 400 | The request body is not valid JSON. |
INVALID_REQUEST_BODY | 400 | The request body does not match the expected object structure. |
METHOD_NOT_ALLOWED | 405 | This endpoint does not accept that HTTP method; the Allow header lists the ones it does. |
UNSUPPORTED_MEDIA_TYPE | 415 | This endpoint does not accept that Content-Type. Send application/json. |
FEATURE_NOT_ENABLED | 400 | This feature is not enabled for the organization. |
UNAUTHENTICATED | 401 | Authentication is required. Supply valid credentials. |
FORBIDDEN | 403 | You do not have permission to perform this action. |
NOT_FOUND | 404 | The requested resource was not found. |
CONFLICT | 409 | The request conflicts with the current state of the resource. |
RATE_LIMITED | 429 | Too many requests; try again later. |
INTERNAL_ERROR | 500 | An unexpected error occurred. |
SERVICE_UNAVAILABLE | 503 | The service is temporarily unavailable; try again shortly. |
REQUEST_FAILED | 200 con error: | The request could not be completed. |
INVALID_CREDENTIAL | 401 | The supplied credential is malformed or not recognized. |
CREDENTIAL_EXPIRED | 401 | The supplied credential has expired. |
CREDENTIAL_REVOKED | 401 | The supplied credential has been revoked. |
CERTIFICATE_MISMATCH | 401 | The client certificate does not match the supplied credential. |
INVALID_AUTH_ORG | 400 | The x-auth-org header is not a valid organization UUID. |
DOMAIN_NOT_ALLOWED | 403 | Requests from this domain are not allowed for this credential. |
DEVICE_SCOPE_MISMATCH | 403 | This token is scoped to a different device. |
ROLE_NOT_PERMITTED | 403 | Your role does not permit access to this organization. |
FUNCTIONALITY_REQUIRED | 403 | Your role is missing a functionality this action requires. |
PARTNER_FUNCTIONALITY_REQUIRED | 403 | Your partner role is missing a functionality this action requires. |
PERMISSION_REQUIRED | 403 | Your role does not have the required permission on the requested resource. |
PARTNER_CLIENT_ACCESS_DENIED | 403 | Your partner organization does not have access to this client organization. |
HARDWARE_NOT_AVAILABLE | 403 | No hardware with this serial number is available for your organization to register. |
HARDWARE_ALREADY_REGISTERED | 403 | This hardware is already registered to an organization. Remove it from that organization before registering it again. |
LOCATION_NOT_FOUND | 400 | The location was not found in the target organization. |
PARTNER_PORTAL_NOT_ENABLED | 403 | The partner portal is not enabled for your partner organization. Contact Rhombus support. |
MANAGED_BY_MSP | 403 | This organization is managed by a Managed Service Provider. Contact the provider or Rhombus support. |
SUPER_ADMIN_RESTRICTED_BY_MSP | 403 | The Super Admin role in this organization is managed by a Managed Service Provider. Contact the provider or Rhombus support. |
MSP_DESIGNATION_INVALID | 403 | This operation requires an active, signed Managed Service Provider designation. |
NOT_MANAGED_CLIENT | 403 | This organization is not a managed client of your Managed Service Provider account. |
DEVICES_STILL_REGISTERED | 200 con error: | Devices are still registered in this client organization. |
LICENSES_STILL_ASSIGNED | 200 con error: | Licenses you own are still assigned to devices in this client organization. |
DOOR_BOUND_HARDWARE | 200 con error: | This device is bound to one or more access-controlled doors. Remove the bindings in the source organization before moving it. |
CONFIRM_NAME_MISMATCH | 409 | The confirmation text does not match the client organization name. |
DUPLICATE_CLIENT_NAME | 409 | You already manage a client organization with this name. Set allowDuplicateName to create it anyway. |
LICENSE_V2_DISABLED | 409 | Pooled licenses cannot be assigned to this client organization because it is not on license entitlements. Contact Rhombus support. |
INSUFFICIENT_POOL_LICENSES | 409 | Not enough unassigned licenses of this product in the pool. |
Los campos con valor nulo se omiten en las respuestas, así que trata un campo ausente como nulo. Las listas vacías sí se envían.
Códigos de estado
| Estado | Cuerpo | Significado | ¿Reintentar? |
|---|---|---|---|
200 con "error": | {"error":true,"errorMsg":"…", | La acción falló o se rechazó | No; actúa según errorMsg |
400 | {"msg":"Invalid | El cuerpo no es JSON válido | No |
400 | {"msg":"JSON | Un campo tiene el tipo equivocado, un valor de enum no se reconoce o (en algunas operaciones) el cuerpo trae un campo que la operación no acepta | No |
400 | {"msg":"Request o {"error":true,"errorMsg":"[Error: | Falta un campo obligatorio o un valor está fuera de rango. El mensaje nombra el campo | No |
400 | {"errorCode":"INVALID_AUTH_ORG", | El encabezado x-auth-org está mal formado | No |
401 | {"errorCode":"UNAUTHENTICATED", u otro código de credencial | La credencial falta, está mal formada, es desconocida, venció o fue revocada | No; consulta Errores de autenticación y de permisos |
403 | {"errorCode":"PERMISSION_REQUIRED", u otro código de permiso | La credencial es válida, pero no tiene permiso para hacer esto | No; consulta Errores de autenticación y de permisos |
404 | {"msg":"Not | La ruta /api no existe (NOT_FOUND; las rutas distinguen mayúsculas y minúsculas, así que una ruta con mayúsculas incorrectas también recibe 404), o algo que la operación busca no existe | No |
405 | {"errorCode":"METHOD_NOT_ALLOWED", | La ruta no acepta este método HTTP | No |
415 | {"errorCode":"UNSUPPORTED_MEDIA_TYPE", | El Content-Type de la solicitud no es uno que la operación acepte | No |
429 | {"errorCode":"RATE_LIMITED", o un errorMsg que nombra las cámaras | El límite de tasa de tu organización o un límite por cámara para clips y fotogramas | Sí, después de Retry-After; consulta Límites de tasa |
500 | {"msg":"…", | Un error inesperado del servidor | Sí, unas pocas veces con espera exponencial |
503 | {"errorCode":"SERVICE_UNAVAILABLE", | Rhombus no pudo comprobar la credencial en ese momento | Sí, con espera exponencial |
502, 503, 504 | Varía | Un problema temporal de la puerta de enlace o de disponibilidad | Sí, con espera exponencial |
La mayoría de los rechazos llegan como 200 con "error": true; solo algunas operaciones responden 404. Maneja ambos casos.
Errores de autenticación y de permisos
401 significa que Rhombus no pudo establecer quién llama. 403 significa que sí pudo, y que ese llamador no tiene permiso para hacer esto. Ninguno tiene éxito si lo repites sin cambios: corrige la credencial, el rol o la solicitud. El único caso que se reintenta es una credencial vencida, después de renovarla.
| Estado | errorCode | Causa |
|---|---|---|
401 | UNAUTHENTICATED | No se presentó ninguna credencial |
401 | INVALID_CREDENTIAL | La credencial está mal formada o no se reconoce, incluida una clave de API eliminada o un token federado revocado |
401 | CREDENTIAL_EXPIRED | La credencial venció. Renueva el token de acceso OAuth, emite un nuevo token federado o reemplaza la clave de API, y reintenta |
401 | CREDENTIAL_REVOKED | Se revocó la autorización de OAuth detrás del access token |
401 | CERTIFICATE_MISMATCH | El certificado de cliente no pertenece a la clave de API (Client ) |
403 | PERMISSION_REQUIRED | El rol de la credencial, o los scopes de un token de acceso OAuth, no permiten esta operación o este dispositivo |
403 | PARTNER_CLIENT_ACCESS_DENIED | La credencial de partner no puede actuar en la organización indicada en x-auth-org |
403 | DOMAIN_NOT_ALLOWED | Un token federado emitido con un domain llegó desde otro sitio: el Origin de la solicitud, o su Referer cuando no hay Origin, no coincide |
403 | DEVICE_SCOPE_MISMATCH | Un token federado limitado a un dispositivo se usó para otro dispositivo o para una solicitud que no está ligada a su dispositivo |
Scope de OAuth faltante. Cuando a un token de acceso OAuth le falta el scope que necesita una operación, el 403 nombra el scope en un encabezado WWW-Authenticate. Pide ese scope cuando el usuario inicia sesión (Iniciar sesión con Rhombus):
HTTP/1.1 403 Forbidden
WWW-Authenticate: Bearer error="insufficient_scope", scope="physical"Enlaces compartidos protegidos con contraseña. Una solicitud a un enlace compartido protegido con contraseña sin la contraseña correcta recibe 401 con WWW-Authenticate: Basic.
Autenticación indica qué revisar para cada tipo de credencial.
Cuerpos de solicitud
- Envía
POSTconContent-Type: application/jsony un objeto JSON,{}cuando la operación no recibe parámetros. - Los nombres de campo distinguen mayúsculas y minúsculas, y la mayoría de las operaciones ignoran los campos que no reconocen. Un campo mal escrito o con mayúsculas incorrectas se descarta sin error, así que un filtro que parece no tener efecto suele ser un error de escritura. Copia los nombres de campo de la referencia de la API (en inglés).
- Los valores de enum distinguen mayúsculas y minúsculas. Un valor no reconocido normalmente se rechaza con
400, pero algunos campos lo sustituyen por su valor predeterminado, así que un error de escritura puede cambiar una configuración. Envía los valores exactamente como los lista la referencia. - Las marcas de tiempo son milisegundos de época Unix en los campos que terminan en
Ms. Los campos que terminan enSec,SecsoSecondsestán en segundos. - Los mensajes de validación usan los nombres de campo del servidor, que pueden llevar un guion bajo inicial:
[Error: [_targetUuid]: unexpected null value for required field]se refiere atargetUuid.
Reintentos y espera exponencial
Reintenta solo lo que puede tener éxito sin cambios:
| Respuesta | Acción |
|---|---|
2xx con "error": | Éxito |
2xx con "error": | No reintentes sin cambios; corrige la solicitud o los datos que describe errorMsg |
400, 401, 403, 404, 405, 415 | No reintentes; corrige la solicitud, la credencial o el rol |
401 con CREDENTIAL_EXPIRED | Renueva la credencial y reintenta |
429 | Espera los segundos de Retry-After y reintenta |
500, 502, 503, 504, un tiempo de espera agotado o una conexión cortada | Reintenta con espera exponencial y jitter |
Cómo esperar:
- Limitación (
429): el servidor indica cuánto esperar enRetry-After, tanto para el límite de tu organización como para los límites por cámara de clips y fotogramas. Espera al menos ese tiempo, más un poco de jitter aleatorio para que muchos procesos no reintenten en el mismo instante. Reintentar antes de tiempo con una cámara limitada alarga su siguiente espera. - Fallos temporales (
5xx, errores de red): espera de forma exponencial con jitter completo: un tiempo aleatorio entre 0 ymin(60 s, 1 s × 2^intento). - Limita cada ciclo de reintentos con un número máximo de intentos (unos 5) y un presupuesto de tiempo, y después informa el fallo con su estado,
errorCode,errorMsgytxid. - Comprueba antes de repetir una escritura. Si una solicitud que crea algo (un clip, una clave, un usuario) agotó el tiempo o falló con un
5xx, puede haber tenido éxito igualmente; busca el resultado antes de enviarla de nuevo para no crear un duplicado. - Comparte el presupuesto. Todas las claves y tokens de tu organización consumen el mismo límite de tasa, así que muchos procesos reintentando a la vez cuentan juntos; reparte las tareas programadas en lugar de iniciarlas todas en el mismo minuto.
Un cliente que sigue estas reglas:
import random
import time
import requests
RETRYABLE = {429, 500, 502, 503, 504}
session = requests.Session()
session.headers.update({"x-auth-scheme": "api-token", "x-auth-apikey": "YOUR_API_KEY"})
def call(url, body, attempts=5, base=1.0, cap=60.0, budget=120.0):
"""POST to the Rhombus API, retrying throttling and temporary failures."""
deadline = time.monotonic() + budget
for attempt in range(attempts):
try:
response = session.post(url, json=body, timeout=30)
except (requests.ConnectionError, requests.Timeout):
response = None # network trouble: retry like a 5xx
if response is not None and response.status_code not in RETRYABLE:
response.raise_for_status() # other 4xx: fix the request, don't retry
data = response.json()
if data.get("error"):
raise RuntimeError(data.get("errorMsg")) # refused: don't retry unchanged
return data
if response is not None and response.status_code == 429:
delay = float(response.headers.get("Retry-After", "1")) + random.uniform(0, 1)
else:
delay = random.uniform(0, min(cap, base * 2 ** attempt))
if attempt == attempts - 1 or time.monotonic() + delay > deadline:
break
time.sleep(delay)
raise RuntimeError(f"gave up after {attempt + 1} attempts: {url}")const RETRYABLE = new Set([429, 500, 502, 503, 504]);
const headers = { "x-auth-scheme": "api-token", "x-auth-apikey": "YOUR_API_KEY", "Content-Type": "application/json" };
const sleep = (ms) => new Promise((resolve) => setTimeout(resolve, ms));
export async function call(url, body, { attempts = 5, baseMs = 1000, capMs = 60000, budgetMs = 120000 } = {}) {
const deadline = Date.now() + budgetMs;
for (let attempt = 0; attempt < attempts; attempt++) {
let response;
try {
response = await fetch(url, { method: "POST", headers, body: JSON.stringify(body), signal: AbortSignal.timeout(30000) });
} catch {
response = undefined; // network trouble: retry like a 5xx
}
if (response && !RETRYABLE.has(response.status)) {
if (!response.ok) throw new Error(`HTTP ${response.status}`); // other 4xx: fix the request
const data = await response.json();
if (data.error) throw new Error(data.errorMsg); // refused: don't retry unchanged
return data;
}
const delay =
response?.status === 429
? Number(response.headers.get("Retry-After") ?? 1) * 1000 + Math.random() * 1000
: Math.random() * Math.min(capMs, baseMs * 2 ** attempt);
if (attempt === attempts - 1 || Date.now() + delay > deadline) break;
await sleep(delay);
}
throw new Error(`gave up: ${url}`);
}Contactar al soporte
Cuando un error persiste, escribe a support@rhombus.com con:
- La ruta de la operación y la hora de la solicitud, con su zona horaria.
- Tu región (EE. UU. o UE).
- El código de estado y el cuerpo, incluidos
errorCodeyerrorMsg. - El ID de la solicitud:
txiden el cuerpo del error, que también se envía como encabezado de respuestax-rhombus-txid.
Nunca envíes tu clave de API, tus tokens OAuth ni tus tokens federados.