
# Crear un enlace de Admin Portal

`POST /v1/admin-portal-links` · permiso `admin-portal-links:write`

Crea un enlace que permite al administrador de TI de una de tus organizaciones clientes configurar SSO, la sincronización de directorios, la verificación de dominios y más en el Admin Portal alojado, sin pasar por tu equipo de soporte. Genera un enlace nuevo cada vez que tu administrador haga clic en «Configurar SSO» en tu aplicación y redirígelo a `url` de inmediato: el enlace es válido durante 5 minutos y abre una única sesión del portal, una sola vez. `url` solo se devuelve en esta respuesta, una vez; ningún endpoint la devuelve después. La respuesta incluye `Cache-Control: no-store`, y una respuesta reenviada para el mismo `Idempotency-Key` omite `url` e incluye `secret_redacted: true`. La sesión del portal dura 60 minutos de inactividad, con un máximo de 8 horas; revócala con `DELETE /v1/admin-portal-links/{link_id}`. La creación se audita como `admin_portal_link.created` (la URL nunca entra en el registro de auditoría).

`audit_logs` requiere el plan Business y `log_streams` el plan Enterprise; las demás intenciones están disponibles en todos los planes. `delivery: email` envía una invitación en su lugar: dura 7 días y se envía por correo a `recipient_email`; la respuesta no incluye `url` ni dirección. Cuando el destinatario la abre, se envía un código de 6 dígitos (válido 10 minutos) a la misma dirección, y solo el código verificado abre la sesión del portal. Si no se puede enviar el correo de invitación, la llamada responde `503 service_unavailable` con `Retry-After` y no crea nada. Revoca una invitación con `DELETE /v1/admin-portal-links/{link_id}`. La dirección del destinatario nunca aparece en una respuesta, una lista, el registro de auditoría ni un log.

## Solicitud

| Campo | Tipo | Obligatorio | Descripción |
|---|---|---|---|
| `organization_id` | string | sí | ID de la organización (`org_…`); debe existir en este entorno. |
| `intents` | string[] | sí | De 1 a 6 valores de `sso`, `dsync`, `domain_verification`, `certificate_renewal`, `audit_logs` (Business), `log_streams` (Enterprise), sin repetir ninguno. |
| `return_url` | string | sí | URL absoluta `https` de como máximo 2048 bytes, sin información de usuario ni fragmento. Su origen debe ser el origen de un `redirect_uri` de una aplicación activa registrada en este entorno. |
| `delivery` | string | no | `link` (predeterminado; `url` de un solo uso válida 5 minutos en la respuesta) o `email` (invitación válida 7 días enviada por correo a `recipient_email`; sin `url`). |
| `recipient_email` | string | no | Solo con `delivery: email`, y obligatorio en ese caso; como máximo 320 caracteres. Si falta: `422` `/recipient_email` `required`; con `delivery: link`: `not_allowed`; dirección no válida: `invalid_value`. |

## Respuesta

`201 Created` con el enlace y su `url` de un solo uso.

```json
{
  "id": "apl_01j9xv2c4d6e8f0g2h4j6k8m0n",
  "organization_id": "org_01j9xk3m7q8r2s4t6v8w0y2z4a",
  "intents": [
    "dsync",
    "sso"
  ],
  "delivery": "link",
  "return_url": "https://app.example.com/settings/sso",
  "expires_at": "2026-10-12T09:36:00.123456Z",
  "consumed_at": null,
  "revoked_at": null,
  "created_at": "2026-10-12T09:31:00.123456Z",
  "url": "https://acme.eu.keenauth.app/portal/l/<token>"
}
```

## Errores

| HTTP | code | Cuándo |
|---|---|---|
| 403 | `plan_feature_unavailable` | Una intención necesita un plan superior; `feature` y `required_plan` lo indican. |
| 404 | `not_found` | La organización no existe en este entorno. |
| 409 | `organization_disabled` | La organización no está activa (deshabilitada); no se crea ningún enlace. |
| 409 | `organization_pending_deletion` | La organización está programada para eliminación; no se crea ningún enlace. |
| 422 | `validation_failed` | Falta un campo o no es válido; `/return_url` `origin_not_registered` cuando su origen no es el de una aplicación registrada; `/recipient_email` `required`, `not_allowed` o `invalid_value`. |
| 401 | `unauthenticated` | La credencial falta, no es válida o fue revocada. |
| 403 | `insufficient_permissions` | La credencial no tiene el permiso necesario. |
| 429 | `rate_limited` | Se superó el límite de frecuencia; reintenta después del periodo `Retry-After`. |
| 503 | `service_unavailable` | Una dependencia no está disponible temporalmente, o (`delivery: email`) no se puede enviar el correo de invitación; no se crea nada. Reintenta después del periodo `Retry-After`. |

## Ejemplo

```bash
curl -i -X POST "https://api.eu.keenauth.com/v1/admin-portal-links" \
  -H "Authorization: Bearer $KEENAUTH_SECRET_KEY" \
  -H "Content-Type: application/json" \
  -d '{"organization_id":"org_01j9xk3m7q8r2s4t6v8w0y2z4a","intents":["sso","dsync"],"return_url":"https://app.example.com/settings/sso"}'
```

### Enviar una invitación por correo

```bash
curl -i -X POST "https://api.eu.keenauth.com/v1/admin-portal-links" \
  -H "Authorization: Bearer $KEENAUTH_SECRET_KEY" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: $(uuidgen)" \
  -d '{"organization_id":"org_01j9xk3m7q8r2s4t6v8w0y2z4a","intents":["sso"],"return_url":"https://app.example.com/settings/sso","delivery":"email","recipient_email":"it@customer.example"}'
```

`201 Created`; la respuesta no incluye `url` ni dirección:

```json
{
  "id": "apl_01j9xv2c4d6e8f0g2h4j6k8m0n",
  "organization_id": "org_01j9xk3m7q8r2s4t6v8w0y2z4a",
  "intents": [
    "sso"
  ],
  "delivery": "email",
  "return_url": "https://app.example.com/settings/sso",
  "expires_at": "2026-10-19T09:31:00.123456Z",
  "consumed_at": null,
  "revoked_at": null,
  "created_at": "2026-10-12T09:31:00.123456Z"
}
```

Con la CLI: `keenauth admin-portal-links create --organization-id org_01j9xk3m7q8r2s4t6v8w0y2z4a --intents sso --return-url https://app.example.com/settings/sso --delivery email --recipient-email it@customer.example`.
