Autenticación
Iniciar sesión con Rhombus
Agrega Iniciar sesión con Rhombus a tu aplicación con OAuth 2.0 y PKCE: registra un cliente, solicita scopes, gestiona códigos de autorización, access tokens y refresh tokens, y llama a la API con un token Bearer.
En esta página
- Descripción general
- Cómo funciona el flujo
- Antes de comenzar
- Paso 1: Registra tu aplicación
- Registrarse con la API
- Registro dinámico de clientes
- Paso 2: Construye la URL de autorización
- Paso 3: Maneja el callback de redirección
- Paso 4: Intercambia el código por un access token
- Paso 5: Llama a la API de Rhombus
- Scopes
- Tiempo de vida del access token
- Refresh tokens
- Revocar una autorización
- Emite una API key de larga duración
- Trabajar con cuentas partner
- Emite una clave de API de partner
- Implementación de referencia
- Solución de problemas
- Próximos pasos
Esta página se tradujo automáticamente. Si encuentras errores o tienes sugerencias, escríbenos.
Descripción general
Rhombus soporta OAuth 2.0 con PKCE para que puedas crear aplicaciones que inicien sesión con las credenciales existentes de Rhombus. Cuando un usuario hace clic en Iniciar sesión con Rhombus en tu aplicación, es redirigido a la Consola de Rhombus para autenticarse, y luego es devuelto a tu URI de redirección con un código de autorización de corta duración. Intercambias ese código por un access token y llamas a la API de Rhombus en nombre del usuario.
Este es el mismo flujo utilizado por el Rhombus CLI oficial: rhombus login es una implementación de referencia funcional en Go que puedes leer de principio a fin.
Usa esta guía para construir:
- Herramientas CLI que se autentican mediante inicio de sesión por navegador (como el propio Rhombus CLI)
- Aplicaciones web que permiten a los usuarios de Rhombus iniciar sesión en tu servicio
- Paneles de administración e integraciones internas para clientes que gestionan muchas organizaciones de Rhombus
- Aplicaciones de escritorio usando una redirección por loopback local
Lo que esta guía no es. Esta guía es para desarrolladores externos que crean aplicaciones que inician sesión a usuarios de Rhombus. Si eres un cliente de Rhombus que intenta configurar SAML SSO para tus empleados (Okta, Azure AD, Google Workspace) o SCIM para el aprovisionamiento de usuarios, consulta Aprovisionamiento SAML SSO y SCIM en su lugar: esa es una superficie distinta del flujo OAuth descrito aquí.
Cómo funciona el flujo
La superficie OAuth de Rhombus abarca tres hosts. Esta es una fuente común de confusión: tu aplicación se comunica con los tres en distintas etapas del flujo.
| Rol | Host de EE. UU. | Host de la UE |
|---|---|---|
| Donde el usuario inicia sesión y aprueba tu aplicación | console.rhombussystems.com | console.eu.rhombussystems.com |
| Endpoints de tokens, refresh, revocación y registro dinámico | auth-web.rhombussystems.com | auth-web.eu.rhombussystems.com |
| Llamadas a la API realizadas con el access token resultante | api2.rhombussystems.com | api2.eu.rhombussystems.com |
El diagrama y los ejemplos de código de esta guía usan los hosts de EE. UU. Para las organizaciones de la UE, sustituye los hosts .eu. en todo el flujo. Las aplicaciones OAuth están vinculadas a la región: la llamada de registro, la URL de autorización y el intercambio de tokens deben apuntar a la misma región que la organización del usuario que inicia sesión — un desajuste falla con "This application is not available for your organization".
Antes de comenzar
Antes de comenzar, asegúrate de tener:
- Una cuenta de Rhombus, más una clave de API (generada en la Consola de Rhombus en Configuración → Integraciones y recursos para desarrolladores → Tokens de API) si registras tu aplicación con la API; el registro dinámico de clientes no necesita clave
- Una URI de redirección que tu aplicación controle: una URL HTTPS pública en producción, o
http://localhost:<port>/callbackpara aplicaciones CLI y de escritorio - Familiaridad básica con el flujo de código de autorización OAuth 2.0 y PKCE (RFC 7636)
La persona que autoriza tu aplicación necesita un rol que pueda gestionar claves de API (o acceso completo). Los usuarios sin ese permiso pueden iniciar sesión en la consola, pero no pueden aprobar la aplicación.
Paso 1: Registra tu aplicación
Antes de poder iniciar un flujo OAuth, Rhombus necesita conocer tu aplicación. El registro te entrega un clientId y, para un cliente confidencial, un clientSecret. Un cliente público (una CLI o una app de escritorio que no puede guardar un secreto) se registra con tokenEndpointAuthMethod: "none" y no recibe ningún secreto.
Hay dos formas de obtener un clientId:
Llama a submitApplication con una clave de API de tu organización. Más adelante puedes listar, actualizar y eliminar la aplicación con los endpoints de OAuth.
La aplicación se registra sola en /oauth/register, sin clave de API. Pensado para herramientas de línea de comandos, aplicaciones de escritorio y clientes MCP que se registran en su primera ejecución.
Registrarse con la API
POST /api/oauth/submitApplication devuelve un clientId y clientSecret nuevos. Almacena el clientSecret de forma segura: no se puede recuperar más tarde.
import requests
headers = {
"x-auth-scheme": "api-token",
"x-auth-apikey": "YOUR_API_KEY",
"Content-Type": "application/json",
}
response = requests.post(
"https://api2.rhombussystems.com/api/oauth/submitApplication",
headers=headers,
json={
"name": "Acme Console",
"description": "Acme internal dashboard for Rhombus operators",
"contactEmail": "engineering@acme.example",
"redirectUri": "https://acme.example/oauth/callback",
},
)
data = response.json()
print("clientId: ", data["clientId"])
print("clientSecret:", data["clientSecret"]) # store securely — not retrievable laterconst response = await fetch(
"https://api2.rhombussystems.com/api/oauth/submitApplication",
{
method: "POST",
headers: {
"x-auth-scheme": "api-token",
"x-auth-apikey": "YOUR_API_KEY",
"Content-Type": "application/json",
},
body: JSON.stringify({
name: "Acme Console",
description: "Acme internal dashboard for Rhombus operators",
contactEmail: "engineering@acme.example",
redirectUri: "https://acme.example/oauth/callback",
}),
}
);
const { clientId, clientSecret } = await response.json();
console.log("clientId: ", clientId);
await saveToSecretsManager("rhombus-oauth-client-secret", clientSecret); // shown only once; never log itpackage main
import (
"bytes"
"encoding/json"
"fmt"
"net/http"
)
func main() {
body, _ := json.Marshal(map[string]string{
"name": "Acme Console",
"description": "Acme internal dashboard for Rhombus operators",
"contactEmail": "engineering@acme.example",
"redirectUri": "https://acme.example/oauth/callback",
})
req, _ := http.NewRequest("POST",
"https://api2.rhombussystems.com/api/oauth/submitApplication",
bytes.NewReader(body))
req.Header.Set("x-auth-scheme", "api-token")
req.Header.Set("x-auth-apikey", "YOUR_API_KEY")
req.Header.Set("Content-Type", "application/json")
resp, _ := http.DefaultClient.Do(req)
defer resp.Body.Close()
var result struct {
ClientID string `json:"clientId"`
ClientSecret string `json:"clientSecret"`
}
json.NewDecoder(resp.Body).Decode(&result)
fmt.Println("clientId: ", result.ClientID)
fmt.Println("clientSecret:", result.ClientSecret) // store securely
}Ejemplo de respuesta:
{
"clientId": "PJjjlcKAQPCzIcaeprzEVg",
"clientSecret": "YOUR_CLIENT_SECRET"
}Hay endpoints adicionales disponibles para gestionar aplicaciones registradas: getAllApplicationsForOrg, getApplicationByClientId, updateApplication y deleteApplication. Consulta la Referencia de la API bajo la etiqueta OAuth. Eliminar una aplicación revoca todas las autorizaciones que los usuarios le dieron.
Registro dinámico de clientes
Una aplicación también puede registrarse sola con el registro dinámico de clientes de OAuth 2.0 (RFC 7591): envía un cuerpo JSON a https://auth-web.rhombussystems.com/oauth/register, sin clave de API. Registra un cliente público ("token_endpoint_auth_method": "none") cuando la aplicación no pueda guardar un secreto; en ese caso depende solo de PKCE.
curl -X POST "https://auth-web.rhombussystems.com/oauth/register" \
-H "Content-Type: application/json" \
-d '{
"client_name": "Acme CLI",
"redirect_uris": ["http://localhost:8765/callback"],
"token_endpoint_auth_method": "none"
}'La respuesta incluye el nuevo client_id, y un client_secret salvo que hayas registrado un cliente público. Guarda el client_id y reutilízalo en los siguientes inicios de sesión en lugar de registrarte de nuevo.
El registro anónimo está limitado a 10 registros por hora desde una misma dirección IP de cliente y a 100 por hora en total. Si se supera cualquiera de los dos límites, el endpoint devuelve 429 Too Many Requests con un encabezado Retry-After y un cuerpo de error de OAuth cuyo error es temporarily_unavailable; espera los segundos que indica Retry-After antes de volver a intentarlo.
Paso 2: Construye la URL de autorización
Rhombus usa PKCE (Proof Key for Code Exchange) para protegerse contra la interceptación de códigos de autorización. Para cada intento de inicio de sesión, genera:
- Un code verifier: una cadena aleatoria URL-safe de 43 a 128 caracteres
- Un code challenge: el hash SHA-256 del verifier, codificado en base64url (sin padding)
- Un parámetro state: un valor aleatorio impredecible utilizado para prevenir CSRF
Conserva el codeVerifier y el state junto con la sesión del usuario (o, para herramientas CLI, en memoria del proceso) hasta que llegue el callback. Necesitarás ambos.
Indica en scope el acceso que necesita tu aplicación, separado por espacios (consulta Scopes). Los ejemplos solicitan los tres scopes porque más adelante esta guía emite una clave de API; una aplicación que solo lee solicita scope=read.
import base64
import hashlib
import secrets
from urllib.parse import urlencode
def new_pkce_pair():
verifier = secrets.token_urlsafe(64) # 86 chars after base64
digest = hashlib.sha256(verifier.encode("ascii")).digest()
challenge = base64.urlsafe_b64encode(digest).rstrip(b"=").decode("ascii")
return verifier, challenge
client_id = "PJjjlcKAQPCzIcaeprzEVg"
redirect_uri = "https://acme.example/oauth/callback"
code_verifier, code_challenge = new_pkce_pair()
state = secrets.token_urlsafe(32)
# Save code_verifier + state in the user's session before redirecting.
params = {
"client_id": client_id,
"redirect_uri": redirect_uri,
"response_type": "code",
"scope": "read write physical",
"state": state,
"code_challenge": code_challenge,
"code_challenge_method": "S256",
}
authorize_url = f"https://console.rhombussystems.com/oauth/authorize?{urlencode(params)}"
print(authorize_url)import crypto from "node:crypto";
function newPkcePair() {
const verifier = crypto.randomBytes(64).toString("base64url");
const challenge = crypto
.createHash("sha256")
.update(verifier)
.digest("base64url");
return { verifier, challenge };
}
const clientId = "PJjjlcKAQPCzIcaeprzEVg";
const redirectUri = "https://acme.example/oauth/callback";
const { verifier: codeVerifier, challenge: codeChallenge } = newPkcePair();
const state = crypto.randomBytes(32).toString("base64url");
// Save codeVerifier + state in the user's session before redirecting.
const params = new URLSearchParams({
client_id: clientId,
redirect_uri: redirectUri,
response_type: "code",
scope: "read write physical",
state,
code_challenge: codeChallenge,
code_challenge_method: "S256",
});
const authorizeUrl = `https://console.rhombussystems.com/oauth/authorize?${params}`;
console.log(authorizeUrl);package main
import (
"crypto/rand"
"crypto/sha256"
"encoding/base64"
"fmt"
"net/url"
)
func newPkcePair() (verifier, challenge string) {
b := make([]byte, 64)
rand.Read(b)
verifier = base64.RawURLEncoding.EncodeToString(b)
sum := sha256.Sum256([]byte(verifier))
challenge = base64.RawURLEncoding.EncodeToString(sum[:])
return
}
func randomState() string {
b := make([]byte, 32)
rand.Read(b)
return base64.RawURLEncoding.EncodeToString(b)
}
func main() {
clientID := "PJjjlcKAQPCzIcaeprzEVg"
redirectURI := "https://acme.example/oauth/callback"
codeVerifier, codeChallenge := newPkcePair()
state := randomState()
// Save codeVerifier + state in the user's session before redirecting.
params := url.Values{
"client_id": {clientID},
"redirect_uri": {redirectURI},
"response_type": {"code"},
"scope": {"read write physical"},
"state": {state},
"code_challenge": {codeChallenge},
"code_challenge_method": {"S256"},
}
fmt.Println("https://console.rhombussystems.com/oauth/authorize?" + params.Encode())
}El endpoint de autorización usa los nombres de parámetros estándar de OAuth 2.0: client_id, redirect_uri, response_type=code, scope, state, code_challenge y code_challenge_method=S256. Solo se admite S256 como método de challenge. (Por compatibilidad, la consola también acepta las formas abreviadas redirect y challenge, pero prefiere los nombres estándar mostrados arriba.)
Redirige el navegador del usuario a la URL que construiste. El usuario iniciará sesión en Rhombus y aprobará tu aplicación.
Paso 3: Maneja el callback de redirección
Después de que el usuario se autentica, Rhombus redirige a tu redirectUri con parámetros de consulta:
En caso de éxito, el callback incluye:
| Parámetro | Descripción |
|---|---|
code | Código de autorización para intercambiar por un access token. De corta duración. |
state | El valor de state que enviaste — debes verificar que coincida. |
iss | (cuando está configurado) El identificador del emisor (RFC 9207). Si está presente, valida que coincida con el servidor de autorización esperado. |
En caso de fallo, el callback incluye error (p. ej., access_denied) y error_description (detalle legible para humanos) en lugar de code.
Verifica siempre que el parámetro state coincida con lo que enviaste. Una falta de coincidencia indica un posible ataque CSRF: aborta el flujo.
from flask import Flask, request, session, abort
app = Flask(__name__)
@app.route("/oauth/callback")
def callback():
if request.args.get("error"):
return f"Login failed: {request.args['error_description']}", 400
code = request.args.get("code")
state = request.args.get("state")
if state != session.pop("oauth_state", None):
abort(400, "state mismatch")
code_verifier = session.pop("oauth_verifier", None)
# Proceed to Step 4 — exchange `code` for an access token.
...import express from "express";
const app = express();
app.get("/oauth/callback", (req, res) => {
const { code, state, error, error_description } = req.query;
if (error) return res.status(400).send(`Login failed: ${error_description}`);
if (state !== req.session.oauthState) return res.status(400).send("state mismatch");
const codeVerifier = req.session.oauthVerifier;
// Proceed to Step 4 — exchange `code` for an access token.
});// HTTP handler for your redirect URI.
func handleCallback(w http.ResponseWriter, r *http.Request) {
q := r.URL.Query()
if errMsg := q.Get("error"); errMsg != "" {
http.Error(w, "login failed: "+q.Get("error_description"), 400)
return
}
code := q.Get("code")
state := q.Get("state")
if state != session.State(r) {
http.Error(w, "state mismatch", 400)
return
}
codeVerifier := session.Verifier(r)
// Proceed to Step 4 — exchange `code` for an access token.
_, _ = code, codeVerifier
}Paso 4: Intercambia el código por un access token
Llama a POST https://auth-web.rhombussystems.com/oauth/token con el código de autorización y tu verifier PKCE. Esta es una solicitud de token OAuth 2.0 estándar: envía los parámetros codificados en formulario (application/x-www-form-urlencoded), no como JSON, y autentica tu cliente con su clientId/clientSecret, ya sea mediante HTTP Basic auth (client_secret_basic) o en el cuerpo de la solicitud (client_secret_post, mostrado abajo). Un cliente público (registrado con token_endpoint_auth_method none) no tiene secreto y envía solo client_id. Este es un host distinto del de la API principal: el endpoint /oauth/token reside en auth-web.rhombussystems.com.
import requests
token_response = requests.post(
"https://auth-web.rhombussystems.com/oauth/token",
headers={"Content-Type": "application/x-www-form-urlencoded"},
data={
"grant_type": "authorization_code",
"code": code,
"redirect_uri": redirect_uri,
"code_verifier": code_verifier,
"client_id": client_id,
"client_secret": client_secret,
},
)
token_response.raise_for_status()
tokens = token_response.json()
access_token = tokens["access_token"]
refresh_token = tokens["refresh_token"]
expires_in_s = tokens["expires_in"]
granted_scope = tokens["scope"]const tokenResponse = await fetch("https://auth-web.rhombussystems.com/oauth/token", {
method: "POST",
headers: { "Content-Type": "application/x-www-form-urlencoded" },
body: new URLSearchParams({
grant_type: "authorization_code",
code,
redirect_uri: redirectUri,
code_verifier: codeVerifier,
client_id: clientId,
client_secret: clientSecret,
}),
});
if (!tokenResponse.ok) {
throw new Error(`Token exchange failed: ${await tokenResponse.text()}`);
}
const {
access_token: accessToken,
refresh_token: refreshToken,
expires_in: expiresIn,
scope: grantedScope,
} = await tokenResponse.json();form := url.Values{
"grant_type": {"authorization_code"},
"code": {code},
"redirect_uri": {redirectURI},
"code_verifier": {codeVerifier},
"client_id": {clientID},
"client_secret": {clientSecret},
}
req, _ := http.NewRequest("POST",
"https://auth-web.rhombussystems.com/oauth/token",
strings.NewReader(form.Encode()))
req.Header.Set("Content-Type", "application/x-www-form-urlencoded")
resp, err := http.DefaultClient.Do(req)
if err != nil || resp.StatusCode != 200 {
return fmt.Errorf("token exchange failed")
}
defer resp.Body.Close()
var tokens struct {
AccessToken string `json:"access_token"`
RefreshToken string `json:"refresh_token"`
TokenType string `json:"token_type"`
ExpiresIn int `json:"expires_in"`
Scope string `json:"scope"`
}
json.NewDecoder(resp.Body).Decode(&tokens)Ejemplo de respuesta:
{
"access_token": "AAAAAAAAAAAAAAAAAAAAAA",
"refresh_token": "BBBBBBBBBBBBBBBBBBBBBB",
"token_type": "Bearer",
"expires_in": 3600,
"scope": "read write physical"
}scope lista los scopes que tiene el token, separados por espacios.
Paso 5: Llama a la API de Rhombus
Envía el access token en el encabezado estándar en cada llamada a la API de Rhombus:
Authorization: Bearer <accessToken>
Un token Bearer sin encabezado x-auth-scheme se interpreta como un access token OAuth, y Authorization: Bearer no transporta ningún otro tipo de credencial. El mismo encabezado funciona en la API REST (api2), en los hosts de medios y en el handshake del WebSocket de eventos. También se acepta el par de encabezados equivalente x-auth-scheme: api-oauth-token y x-auth-access-token: <accessToken>.
import requests
headers = {
"Authorization": f"Bearer {access_token}",
"Content-Type": "application/json",
}
response = requests.post(
"https://api2.rhombussystems.com/api/user/getUsersInOrg",
headers=headers,
json={},
)
users = response.json().get("users", [])
for user in users:
print(user.get("email"), "—", user.get("name"))const response = await fetch(
"https://api2.rhombussystems.com/api/user/getUsersInOrg",
{
method: "POST",
headers: {
Authorization: `Bearer ${accessToken}`,
"Content-Type": "application/json",
},
body: JSON.stringify({}),
}
);
const { users = [] } = await response.json();
for (const user of users) {
console.log(`${user.email} — ${user.name}`);
}req, _ := http.NewRequest("POST",
"https://api2.rhombussystems.com/api/user/getUsersInOrg",
strings.NewReader("{}"))
req.Header.Set("Authorization", "Bearer "+accessToken)
req.Header.Set("Content-Type", "application/json")
resp, _ := http.DefaultClient.Do(req)
defer resp.Body.Close()
// ... decode responseSi esta llamada devuelve una lista de usuarios, tu flujo OAuth funciona de extremo a extremo. El usuario se autenticó, tienes un access token y estás llamando a la API en su nombre.
Scopes
Un scope limita lo que puede hacer un access token. Solicita los scopes en el parámetro scope de la URL de autorización, separados por espacios (por ejemplo, scope=read%20write). Los valores que Rhombus no reconoce se ignoran, y una solicitud sin scope recibe los tres. La pantalla de consentimiento lista los scopes que pide tu aplicación:
| Scope | Pantalla de consentimiento | Qué cubre |
|---|---|---|
read | Read: ver cámaras, grabaciones y configuración | Operaciones cuyo nombre empieza por get, find, search, list, export, count, query, lookup, fetch, download, view, describe o preview; medios y video en vivo; el WebSocket de eventos |
write | Write: cambiar configuración y datos | Todas las demás operaciones |
physical | Physical: desbloquear puertas y ascensores, confinamiento, alarmas, altavoces y robots | Desbloqueos de puertas y ascensores (incluidos los de integraciones), confinamiento (lockdown), anulaciones de estado, relés, first-in, armar y desarmar, escalar a alarma, reproducción en altavoces, el socket en vivo del robot y el envío de audio de intercomunicación (talk) |
La página de cada operación en la referencia de la API muestra el scope que necesita.
- Los scopes nunca añaden acceso. Un token solo puede hacer lo que permiten a la vez el rol del usuario y sus scopes (consulta Scopes de OAuth).
- El WebSocket de eventos necesita
read. Un token sin ese scope se rechaza en el handshake. - El audio de intercomunicación se comprueba en cada mensaje. Enviar audio sin
physicalcierra el socket con el código 1008. - Crear, rotar y renovar claves de API requiere los tres scopes (consulta Emite una API key de larga duración).
- Las respuestas de tokens incluyen
scope, los scopes que tiene el token. El documento de descubrimiento del servidor de autorización (https://auth-web.rhombussystems.com/.well-known/oauth-authorization-server) y el documento de recurso protegido de la API (https://api2.rhombussystems.com/.well-known/oauth-protected-resource) listan los scopes admitidos enscopes_supported.
Una llamada que necesita un scope que el token no tiene devuelve 403 con errorCode PERMISSION_REQUIRED y un encabezado que indica el scope necesario:
HTTP/1.1 403 Forbidden
WWW-Authenticate: Bearer error="insufficient_scope", scope="physical"Para obtener el scope que falta, vuelve a llevar al usuario por el flujo de autorización con ese scope añadido a scope.
Tiempo de vida del access token
El campo expires_in en la respuesta de tokens te indica cuánto tiempo (en segundos) es válido el access token (típicamente una hora). Cuando expira, la API de Rhombus devuelve 401 con errorCode CREDENTIAL_EXPIRED; refréscalo.
Un cliente que guarda el refresh token mantiene el acceso mientras siga refrescando; la CLI de Rhombus funciona así. Un servicio en segundo plano, un daemon o un trabajo programado que no deba depender del inicio de sesión de un usuario puede, en cambio, emitir una clave de API con el access token (consulta Emite una API key de larga duración).
Refresh tokens
Intercambia el refresh token por un nuevo access token en el mismo endpoint /oauth/token, codificado como formulario:
curl -X POST "https://auth-web.rhombussystems.com/oauth/token" \
-H "Content-Type: application/x-www-form-urlencoded" \
-d grant_type=refresh_token \
-d refresh_token="$REFRESH_TOKEN" \
-d client_id="$CLIENT_ID" \
-d client_secret="$CLIENT_SECRET" # solo clientes confidenciales- Los clientes públicos (registrados con
token_endpoint_auth_methodnone, protegidos solo por PKCE) envían soloclient_id. Los clientes confidenciales envían tambiénclient_secret, en el cuerpo o con HTTP Basic auth. - Cada refresh rota el refresh token. La respuesta incluye un nuevo
refresh_token; guárdalo, porque el que enviaste deja de funcionar. - Nunca reutilices un refresh token reemplazado. Dentro de los 30 segundos posteriores a su reemplazo se rechaza. Pasados 30 segundos, presentarlo revoca toda la autorización, incluidas las claves de API emitidas con ella, y el usuario tiene que volver a iniciar sesión. Si varios procesos comparten una misma sesión, deja que solo uno de ellos refresque.
- Un refresh token sin usar durante 90 días expira.
- Un refresh fallido devuelve
400con el error de OAuthinvalid_grant: el refresh token se reemplazó, expiró o se revocó. Vuelve a llevar al usuario por el flujo de inicio de sesión.
Los access tokens y los refresh tokens son cadenas opacas, no JWT.
Revocar una autorización
Para terminar una autorización, envía el refresh token a https://auth-web.rhombussystems.com/oauth/token/revoke, codificado como formulario, con token, client_id y, en un cliente confidencial, client_secret. Revocar una autorización:
- Detiene sus tokens en un plazo de 60 segundos en todos los hosts. Las conexiones abiertas al WebSocket de eventos reciben un frame STOMP
ERRORy se cierran con el código 1008; los sockets de medios abiertos (video en vivo, intercomunicación, robot, PTZ) se cierran con el código 1008. - Elimina las claves de API emitidas con esa autorización.
- Termina los tokens federados emitidos con esa autorización.
Eliminar la aplicación revoca todas sus autorizaciones. Consulta también Permisos y rotación de claves.
Emite una API key de larga duración
Una vez que un usuario ha iniciado sesión con OAuth, puedes intercambiar el access token de corta duración por una clave de API de larga duración, para un servicio que siga llamando a la API sin la sesión del usuario.
Llama a POST /api/integrations/org/submitApiTokenApplication con Authorization: Bearer <accessToken>. El token necesita los tres scopes (read, write y physical). El cuerpo indica el rol de la nueva clave en permissionGroupUuid: el rol del propio usuario, o uno que tenga permitido asignar. Lista los roles con getPermissionGroups y los roles asignables del usuario con getPermissionsForCurrentUser. Para que la clave caduque, añade expiresAtMs (milisegundos desde epoch). Algunos rechazos, como un rol que el usuario no puede asignar, devuelven HTTP 200 con "error": true y un errorMsg, así que revisa el cuerpo además del código de estado.
import requests
response = requests.post(
"https://api2.rhombussystems.com/api/integrations/org/submitApiTokenApplication",
headers={
"Authorization": f"Bearer {access_token}",
"Content-Type": "application/json",
},
json={
"displayName": "Acme Console — background worker",
"permissionGroupUuid": role_uuid,
"authType": "API_TOKEN",
},
)
data = response.json()
if data.get("error"):
raise RuntimeError(data.get("errorMsg"))
api_key = data["apiKey"] # store this securely — treat it like a passwordconst response = await fetch(
"https://api2.rhombussystems.com/api/integrations/org/submitApiTokenApplication",
{
method: "POST",
headers: {
Authorization: `Bearer ${accessToken}`,
"Content-Type": "application/json",
},
body: JSON.stringify({
displayName: "Acme Console — background worker",
permissionGroupUuid: roleUuid,
authType: "API_TOKEN",
}),
}
);
const data = await response.json();
if (data.error) throw new Error(data.errorMsg);
const { apiKey } = data; // store apiKey securely — treat it like a passwordbody, _ := json.Marshal(map[string]string{
"displayName": "Acme Console — background worker",
"permissionGroupUuid": roleUuid,
"authType": "API_TOKEN",
})
req, _ := http.NewRequest("POST",
"https://api2.rhombussystems.com/api/integrations/org/submitApiTokenApplication",
bytes.NewReader(body))
req.Header.Set("Authorization", "Bearer "+accessToken)
req.Header.Set("Content-Type", "application/json")
// ... execute and decode; store apiKey securely.A partir de ese punto, usa la clave de API con los encabezados estándar x-auth-scheme: api-token + x-auth-apikey: no se requieren más llamadas OAuth. Para una clave de certificado (mTLS), envía "authType": "CERT" con un csr cuya firma sea válida; consulta Claves de API y certificados. El mismo access token puede rotar una clave (POST /api/integrations/org/rotateApiToken) o renovar el certificado de una clave de certificado (POST /api/integrations/org/renewApiTokenCert), con los mismos tres scopes; consulta Gestionar claves con la API.
Trata las claves de API emitidas como contraseñas. Actúan con el rol que indicaste en permissionGroupUuid y funcionan hasta que las eliminas o hasta que pasa su expiresAtMs. Revocar la autorización OAuth con la que se emitieron las elimina. Almacénalas cifradas en reposo, nunca en el control de versiones, y rota o elimina las claves no utilizadas.
Trabajar con cuentas partner
Los usuarios de una organización partner (MSP o revendedor) pueden completar el mismo flujo de "Iniciar sesión con Rhombus", y su access token va en el mismo encabezado Authorization: Bearer <accessToken>. (x-auth-scheme: partner-api-oauth-token con x-auth-access-token se acepta como alias.)
Para actuar dentro de una organización cliente gestionada, agrega x-auth-org: <clientOrgUuid>, exactamente igual que con una clave de API de partner. Es solo un encabezado, y un valor vacío cuenta como ausente. Un token de partner que llama a un endpoint a nivel de cliente sin x-auth-org actúa en la organización partner, y la respuesta incluye los encabezados Deprecation y Sunset: consulta Llamadas a la API Partner.
El callback no indica si el usuario es partner. Si llamas a POST /api/integrations/org/submitApiTokenApplication con el token de un usuario partner, la respuesta es 403 Forbidden. Vuelve a intentarlo con el endpoint de partner que se muestra abajo. La CLI de Rhombus detecta las cuentas partner de la misma forma al iniciar sesión, y rhombus login --partner omite esa comprobación.
Emite una clave de API de partner
Llama a POST /api/partner/submitApiTokenApplication con el access token, sin x-auth-org. El cuerpo es el mismo que el del endpoint de organización. El token necesita los tres scopes, y el rol de partner del usuario debe incluir la administración de la API.
curl -X POST "https://api2.rhombussystems.com/api/partner/submitApiTokenApplication" \
-H "Authorization: Bearer $ACCESS_TOKEN" \
-H "Content-Type: application/json" \
-d '{"displayName": "Acme MSP integration", "permissionGroupUuid": "PARTNER_ROLE_UUID", "authType": "API_TOKEN"}'La respuesta contiene apiKey. Úsala con x-auth-scheme: api-token y x-auth-apikey. Para actuar dentro de una organización cliente gestionada, agrega x-auth-org: <clientOrgUuid>. Consulta Llamadas a la API Partner, que también explica cómo rotar y renovar claves de partner.
Implementación de referencia
El Rhombus CLI es una referencia de producción para todo lo de esta guía. rhombus login inicia sesión con OAuth y mantiene vigentes los tokens del perfil en lugar de crear una clave de API. cmd/login.go recorre todo el flujo de extremo a extremo: registro dinámico como cliente público, generación de PKCE, servidor de callback local, construcción de la URL de autorización, intercambio de tokens y almacenamiento de los tokens; la CLI los refresca antes de que expiren.
Si algo en tu implementación no funciona, compara tu comportamiento contra el CLI: es el ejemplo canónico.
Solución de problemas
state no coincide en el callback
Tu callback recibió un valor de state distinto al que enviaste. Verifica que estás conservando el state que generaste en el Paso 2 junto con la sesión del usuario (o en memoria para herramientas CLI) y comparándolo en el callback. Una falta de coincidencia persistente puede indicar un intento de CSRF: aborta el flujo en lugar de reintentar silenciosamente.
El intercambio de tokens devuelve HTTP 400 o una respuesta con `error`
Causas comunes:
redirect_urino coincide: elredirect_urien el cuerpo del intercambio de tokens debe coincidir exactamente (incluyendo esquema, host, puerto y ruta) con elredirect_urique enviaste en el Paso 2 y la URI registrada con tu aplicación OAuth.codeexpirado o reutilizado: los códigos de autorización expiran a los 5 minutos y solo funcionan una vez. Intercámbialos en el callback.code_verifierno genera el hash decode_challenge: verifica que estás usando SHA-256 y codificación base64url sin padding=tanto en la generación del challenge como en la transmisión del verifier.- Tipo de contenido o autenticación de cliente incorrectos: el endpoint de tokens espera
application/x-www-form-urlencoded(no JSON). Un cliente confidencial envía suclientId/clientSecretmediante HTTP Basic auth o en el cuerpo del formulario (client_secret_post); un cliente público envía soloclient_id. La solicitud de token no usa un encabezadox-auth-scheme.
El refresh devuelve 400 invalid_grant
El refresh token fue reemplazado por un refresh anterior, pasó 90 días sin usarse o su autorización se revocó: mediante el endpoint de revocación, al eliminar la aplicación o porque se presentó un refresh token reemplazado más de 30 segundos después de su reemplazo. Guarda siempre el refresh_token más reciente de cada respuesta. Cuando un refresh falla, vuelve a llevar al usuario por el flujo de inicio de sesión.
Las llamadas a la API fallan con 401 o 403 y un access token
Envía el token como Authorization: Bearer <token> (o con x-auth-scheme: api-oauth-token y x-auth-access-token), nunca en x-auth-apikey. Un 401 significa que el token falta, está mal formado, es desconocido, expiró o fue revocado; errorCode indica cuál (UNAUTHENTICATED, INVALID_CREDENTIAL, CREDENTIAL_EXPIRED, CREDENTIAL_REVOKED). Refresca un token expirado. Un 403 con PERMISSION_REQUIRED significa que el token es válido, pero el rol del usuario o los scopes del token no permiten la llamada; cuando falta un scope, el encabezado WWW-Authenticate lo indica (consulta Scopes). Consulta Cómo interpretar los errores de autenticación.
La redirección termina en la página de inicio de sesión de la consola otra vez sin código
El client_id que estás enviando puede no ser reconocido. Verifica que estás usando el clientId devuelto por submitApplication, no el UUID de aplicación de una respuesta diferente. Si rotaste aplicaciones, el clientId antiguo ya no es válido.
Próximos pasos
Lee cómo el CLI oficial usa este flujo de extremo a extremo
Navega todos los endpoints disponibles una vez que tengas un access token
Comprende los límites de solicitudes antes de lanzar
Obtén ayuda con tu integración.