> ## Documentation Index
> Fetch the complete documentation index at: https://api-docs.rhombus.community/llms.txt
> Use this file to discover all available pages before exploring further.

# Iniciar sesión con Rhombus

> Agrega Iniciar sesión con Rhombus a tu aplicación con OAuth 2.0 y PKCE: gestiona códigos de autorización, tokens de acceso, refresh tokens e identidad SSO.

<Note>
  Esta página fue traducida automáticamente. Si encuentra errores o tiene sugerencias, [contáctenos](mailto:support@rhombus.com).
</Note>

## 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](/es/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

<Note>
  **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](/es/implementations/saml-sso-provisioning) en su lugar: esa es una superficie distinta del flujo OAuth descrito aquí.
</Note>

## 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.

| Host                          | Rol                                                         |
| ----------------------------- | ----------------------------------------------------------- |
| `console.rhombussystems.com`  | Donde el usuario inicia sesión y aprueba tu aplicación      |
| `auth-web.rhombussystems.com` | Endpoint de intercambio de tokens                           |
| `api2.rhombussystems.com`     | Llamadas a la API realizadas con el access token resultante |

```mermaid theme={null}
sequenceDiagram
    participant User
    participant App as Your App
    participant Console as console.rhombussystems.com
    participant Auth as auth-web.rhombussystems.com
    participant API as api2.rhombussystems.com

    User->>App: Click "Sign in with Rhombus"
    App->>App: Generate PKCE verifier + state
    App->>Console: Redirect to /oauth/authorize?client_id=...
    Console->>User: Prompt to sign in and authorize
    User->>Console: Approve
    Console->>App: Redirect to redirect_uri?code=...&state=...
    App->>App: Verify state matches
    App->>Auth: POST /oauth/token (code + verifier)
    Auth->>App: access_token, refresh_token, expires_in
    App->>API: API request with x-auth-access-token
    API->>App: Response
```

## Antes de comenzar

<Info>
  Antes de comenzar, asegúrate de tener:

  * Una **cuenta de Rhombus** con una API key (generada en la [Consola de Rhombus](https://console.rhombussystems.com/) en **Settings → API Management**)
  * Una **URI de redirección** que tu aplicación controle: una URL HTTPS pública en producción, o `http://localhost:<port>/callback` para aplicaciones CLI y de escritorio
  * Familiaridad básica con el **flujo de código de autorización OAuth 2.0** y **PKCE** ([RFC 7636](https://datatracker.ietf.org/doc/html/rfc7636))
</Info>

## Paso 1: Registra tu aplicación

Antes de poder iniciar un flujo OAuth, Rhombus necesita conocer tu aplicación. El registro te entrega un par `clientId` y `clientSecret`.

Hay dos rutas para obtener un `clientId`, dependiendo de la etapa de tu desarrollo:

<Columns cols={2}>
  <Card title="Prototipado y desarrollo" icon="flask">
    Llama a la API `submitApplication` directamente con tu API key existente. Es la ruta más rápida hacia un flujo funcional en `localhost`. Autoservicio, inmediato.
  </Card>

  <Card title="Producción y distribución" icon="shield-check" href="https://rhombus.community">
    Las aplicaciones que se entregarán a clientes o aceptarán inicios de sesión de usuarios fuera de tu propia organización deben ser revisadas por Rhombus. Contacta a tu representante de Rhombus o publica en la [Comunidad de Desarrolladores](https://rhombus.community) para iniciar la revisión.
  </Card>
</Columns>

<Warning>
  Las aplicaciones OAuth de producción requieren revisión de Rhombus. Puedes auto-registrar un `clientId` con la llamada a la API mostrada abajo para desarrollo local y pruebas, pero no distribuyas aplicaciones a usuarios finales con un `clientId` auto-registrado: Rhombus puede aplicar limitación de tasa o revocar el uso en producción no revisado. Inicia una revisión tan pronto como tu prototipo funcione.
</Warning>

### 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.

<CodeGroup>
  ```python Python theme={null}
  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 later
  ```

  ```javascript JavaScript theme={null}
  const 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);
  console.log("clientSecret:", clientSecret); // store securely — not retrievable later
  ```

  ```go Go theme={null}
  package 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
  }
  ```
</CodeGroup>

Ejemplo de respuesta:

```json theme={null}
{
  "clientId": "PJjjlcKAQPCzIcaeprzEVg",
  "clientSecret": "kixFP1l8c55dDt0WdeX8BNwUlnFknGTr9qdn3AYKpsM"
}
```

<Tip>
  Hay endpoints adicionales disponibles para gestionar aplicaciones registradas: `getAllApplicationsForOrg`, `getApplicationByClientId`, `updateApplication` y `deleteApplication`. Consulta la [Referencia de la API](/api-reference/overview) bajo la etiqueta **OAuth**.
</Tip>

## 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.

<CodeGroup>
  ```python Python theme={null}
  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",
      "state":                 state,
      "code_challenge":        code_challenge,
      "code_challenge_method": "S256",
  }
  authorize_url = f"https://console.rhombussystems.com/oauth/authorize?{urlencode(params)}"
  print(authorize_url)
  ```

  ```javascript JavaScript theme={null}
  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",
    state,
    code_challenge: codeChallenge,
    code_challenge_method: "S256",
  });
  const authorizeUrl = `https://console.rhombussystems.com/oauth/authorize?${params}`;
  console.log(authorizeUrl);
  ```

  ```go Go theme={null}
  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"},
          "state":                 {state},
          "code_challenge":        {codeChallenge},
          "code_challenge_method": {"S256"},
      }
      fmt.Println("https://console.rhombussystems.com/oauth/authorize?" + params.Encode())
  }
  ```
</CodeGroup>

<Warning>
  El endpoint de autorización usa los nombres de parámetros estándar de OAuth 2.0: `client_id`, `redirect_uri`, `response_type=code`, `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.)
</Warning>

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](https://datatracker.ietf.org/doc/html/rfc9207)). 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`.

<Warning>
  Verifica siempre que el parámetro `state` coincida con lo que enviaste. Una falta de coincidencia indica un posible ataque CSRF: aborta el flujo.
</Warning>

<CodeGroup>
  ```python Flask theme={null}
  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.
      ...
  ```

  ```javascript Express theme={null}
  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.
  });
  ```

  ```go Go theme={null}
  // 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
  }
  ```
</CodeGroup>

## 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). Este es un host distinto del de la API principal: el endpoint `/oauth/token` reside en `auth-web.rhombussystems.com`.

<CodeGroup>
  ```python Python theme={null}
  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"]
  ```

  ```javascript JavaScript theme={null}
  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,
  } = await tokenResponse.json();
  ```

  ```go Go theme={null}
  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"`
  }
  json.NewDecoder(resp.Body).Decode(&tokens)
  ```
</CodeGroup>

Ejemplo de respuesta:

```json theme={null}
{
  "access_token": "eyJhbGciOi...",
  "refresh_token": "def50200...",
  "token_type": "Bearer",
  "expires_in": 3600
}
```

## Paso 5: Llama a la API de Rhombus

Usa el access token con dos headers en cada llamada a la API de Rhombus:

* `x-auth-scheme: api-oauth-token`
* `x-auth-access-token: <accessToken>`

Esto es distinto del esquema estándar de API key (`api-token` + `x-auth-apikey`): los access tokens OAuth utilizan su propio identificador de esquema para que Rhombus pueda aplicar autorización específica de OAuth.

<CodeGroup>
  ```python Python theme={null}
  import requests

  headers = {
      "x-auth-scheme": "api-oauth-token",
      "x-auth-access-token": 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"))
  ```

  ```javascript JavaScript theme={null}
  const response = await fetch(
    "https://api2.rhombussystems.com/api/user/getUsersInOrg",
    {
      method: "POST",
      headers: {
        "x-auth-scheme": "api-oauth-token",
        "x-auth-access-token": accessToken,
        "Content-Type": "application/json",
      },
      body: JSON.stringify({}),
    }
  );

  const { users = [] } = await response.json();
  for (const user of users) {
    console.log(`${user.email} — ${user.name}`);
  }
  ```

  ```go Go theme={null}
  req, _ := http.NewRequest("POST",
      "https://api2.rhombussystems.com/api/user/getUsersInOrg",
      strings.NewReader("{}"))
  req.Header.Set("x-auth-scheme", "api-oauth-token")
  req.Header.Set("x-auth-access-token", accessToken)
  req.Header.Set("Content-Type", "application/json")

  resp, _ := http.DefaultClient.Do(req)
  defer resp.Body.Close()
  // ... decode response
  ```
</CodeGroup>

<Check>
  Si 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.
</Check>

## 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 devolverá un error de autenticación.

Para acceso de larga duración —servicios en segundo plano, daemons, trabajos programados o cualquier cliente que no pueda volver a solicitar al usuario— **emite una API key duradera** usando el access token OAuth (consulta la siguiente sección) en lugar de intentar mantener una sesión OAuth refrescada. Este es el patrón que utiliza el Rhombus CLI.

<Note>
  Se devuelve un `refresh_token` junto con el access token. Puedes intercambiarlo por un nuevo access token llamando al mismo endpoint `/oauth/token` con `grant_type=refresh_token` y `refresh_token=<token>` (junto con la autenticación de tu cliente). Para acceso de larga duración y no interactivo —servicios en segundo plano, daemons, trabajos programados— prefiere emitir una API key duradera (consulta abajo) en lugar de mantener una sesión OAuth refrescada. Este es el patrón que utiliza el Rhombus CLI.
</Note>

## 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 API key permanente. Esto es lo que hace `rhombus login` para que el CLI pueda continuar realizando llamadas a la API después de que la sesión del navegador termine.

Llama a `POST /api/integrations/org/submitApiTokenApplication` con `x-auth-scheme: api-oauth-token` y `x-auth-access-token: <accessToken>`:

<CodeGroup>
  ```python Python theme={null}
  import requests

  response = requests.post(
      "https://api2.rhombussystems.com/api/integrations/org/submitApiTokenApplication",
      headers={
          "x-auth-scheme": "api-oauth-token",
          "x-auth-access-token": access_token,
          "Content-Type": "application/json",
      },
      json={
          "displayName": "Acme Console — background worker",
          "authType": "API_TOKEN",
      },
  )

  data = response.json()
  api_key = data["apiKey"]  # store this securely — treat it like a password
  ```

  ```javascript JavaScript theme={null}
  const response = await fetch(
    "https://api2.rhombussystems.com/api/integrations/org/submitApiTokenApplication",
    {
      method: "POST",
      headers: {
        "x-auth-scheme": "api-oauth-token",
        "x-auth-access-token": accessToken,
        "Content-Type": "application/json",
      },
      body: JSON.stringify({
        displayName: "Acme Console — background worker",
        authType: "API_TOKEN",
      }),
    }
  );

  const { apiKey } = await response.json();
  // Store apiKey securely — treat it like a password.
  ```

  ```go Go theme={null}
  body, _ := json.Marshal(map[string]string{
      "displayName": "Acme Console — background worker",
      "authType":    "API_TOKEN",
  })

  req, _ := http.NewRequest("POST",
      "https://api2.rhombussystems.com/api/integrations/org/submitApiTokenApplication",
      bytes.NewReader(body))
  req.Header.Set("x-auth-scheme", "api-oauth-token")
  req.Header.Set("x-auth-access-token", accessToken)
  req.Header.Set("Content-Type", "application/json")
  // ... execute and decode; store apiKey securely.
  ```
</CodeGroup>

A partir de ese punto, usa la API key con los headers estándar `x-auth-scheme: api-token` + `x-auth-apikey`: no se requieren más llamadas OAuth. El CLI también soporta una variante basada en certificado (mTLS) de este flujo para implementaciones con mayor seguridad; consulta [`cmd/login.go`](https://github.com/RhombusSystems/rhombus-cli/blob/main/cmd/login.go) para la implementación completa.

<Warning>
  **Trata las API keys emitidas como contraseñas.** Son de larga duración y otorgan los mismos permisos que el usuario que las creó. Almacénalas cifradas en reposo, nunca en el control de versiones, y rota o elimina las claves no utilizadas.
</Warning>

## Trabajar con cuentas partner

"Iniciar sesión con Rhombus" emite tokens OAuth con **alcance de usuario**: actúan dentro de la organización a la que pertenece el usuario. El flujo OAuth no distingue las cuentas partner, y el callback no te indica si el usuario es partner.

Para **escenarios partner/MSP** —donde necesitas actuar sobre varias organizaciones de clientes— usa la **API Partner** en lugar de OAuth. Se basa en una API key con alcance de partner:

* Envía `x-auth-scheme: partner-api-token` con tu API key de partner
* Apunta a una organización cliente específica agregando su UUID en el header `x-auth-org` en cada llamada

Consulta [Llamadas a la API Partner](/es/partner-api-calls) para el patrón completo.

## Implementación de referencia

El [Rhombus CLI](https://github.com/RhombusSystems/rhombus-cli) es una referencia de producción para todo lo de esta guía. [`cmd/login.go`](https://github.com/RhombusSystems/rhombus-cli/blob/main/cmd/login.go) recorre todo el flujo de extremo a extremo: generación de PKCE, servidor de callback local, construcción de la URL de autorización, intercambio de tokens, emisión de API key con mTLS y persistencia de credenciales.

Si algo en tu implementación no funciona, compara tu comportamiento contra el CLI: es el ejemplo canónico.

## Solución de problemas

<AccordionGroup>
  <Accordion title="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.
  </Accordion>

  <Accordion title="El intercambio de tokens devuelve HTTP 400 o una respuesta con `error`">
    Causas comunes:

    * **`redirect_uri` no coincide**: el `redirect_uri` en el cuerpo del intercambio de tokens debe coincidir exactamente (incluyendo esquema, host, puerto y ruta) con el `redirect_uri` que enviaste en el Paso 2 y la URI registrada con tu aplicación OAuth.
    * **`code` expirado**: los códigos de autorización son de corta duración (segundos, no minutos). Intercámbialos inmediatamente en el callback.
    * **`code_verifier` no genera el hash de `code_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), y tu `clientId`/`clientSecret` deben enviarse mediante HTTP Basic auth o en el cuerpo del formulario (`client_secret_post`). La solicitud de token no usa un header `x-auth-scheme`.
  </Accordion>

  <Accordion title="Las llamadas a la API devuelven 401 con un access token válido">
    Revisa los headers. Los access tokens OAuth usan `x-auth-scheme: api-oauth-token` y `x-auth-access-token: <token>`. Usar `x-auth-apikey` (el header de API key) con un access token OAuth fallará: son esquemas distintos con nombres de header distintos.
  </Accordion>

  <Accordion title="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.
  </Accordion>
</AccordionGroup>

## Próximos pasos

<Columns cols={2}>
  <Card title="Rhombus CLI" icon="terminal" href="/es/rhombus-cli">
    Lee cómo el CLI oficial usa este flujo de extremo a extremo
  </Card>

  <Card title="Referencia de la API" icon="code" href="/api-reference/overview">
    Navega todos los endpoints disponibles una vez que tengas un access token
  </Card>

  <Card title="Límites de tasa" icon="gauge" href="/es/rate-limits">
    Comprende los límites de solicitudes antes de lanzar
  </Card>

  <Card title="Comunidad de desarrolladores" icon="users" href="https://rhombus.community">
    Solicita revisión de OAuth de producción y haz preguntas
  </Card>
</Columns>
