
# Créer un lien Admin Portal

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

Crée un lien qui permet à l'administrateur informatique de l'une de vos organisations clientes de configurer SSO, la synchronisation d'annuaire, la vérification de domaine et plus encore dans le Admin Portal hébergé, sans passer par votre équipe de support. Générez un lien neuf chaque fois que votre administrateur clique sur « Configurer SSO » dans votre application, puis redirigez-le immédiatement vers `url` : le lien est valable 5 minutes et ouvre une seule session de portail, une seule fois. `url` n'est renvoyée que dans cette réponse, une seule fois ; aucun point de terminaison ne la renvoie ensuite. La réponse porte `Cache-Control: no-store`, et une réponse rejouée pour le même `Idempotency-Key` omet `url` et porte `secret_redacted: true`. La session de portail dure 60 minutes d'inactivité, 8 heures au maximum ; révoquez-la avec `DELETE /v1/admin-portal-links/{link_id}`. La création est auditée sous `admin_portal_link.created` (l'URL n'entre jamais dans le journal d'audit).

`audit_logs` nécessite le plan Business et `log_streams` le plan Enterprise ; les autres intentions sont disponibles sur tous les plans. `delivery: email` envoie plutôt une invitation : elle reste valable 7 jours et est envoyée par e-mail à `recipient_email` ; la réponse ne contient ni `url` ni adresse. Lorsque le destinataire l'ouvre, un code à 6 chiffres (valable 10 minutes) est envoyé à la même adresse, et seul le code vérifié ouvre la session de portail. Si l'e-mail d'invitation ne peut pas être envoyé, l'appel répond `503 service_unavailable` avec `Retry-After` et ne crée rien. Révoquez une invitation avec `DELETE /v1/admin-portal-links/{link_id}`. L'adresse du destinataire n'apparaît jamais dans une réponse, une liste, le journal d'audit ou un journal.

## Requête

| Champ | Type | Obligatoire | Description |
|---|---|---|---|
| `organization_id` | string | oui | Identifiant de l'organisation (`org_…`) ; elle doit exister dans cet environnement. |
| `intents` | string[] | oui | 1 à 6 valeurs parmi `sso`, `dsync`, `domain_verification`, `certificate_renewal`, `audit_logs` (Business), `log_streams` (Enterprise), sans doublon. |
| `return_url` | string | oui | URL `https` absolue d'au plus 2048 octets, sans informations utilisateur ni fragment. Son origine doit être celle d'un `redirect_uri` d'une application active enregistrée dans cet environnement. |
| `delivery` | string | non | `link` (par défaut ; `url` à usage unique valable 5 minutes dans la réponse) ou `email` (invitation valable 7 jours envoyée à `recipient_email` ; sans `url`). |
| `recipient_email` | string | non | Uniquement avec `delivery: email`, et obligatoire dans ce cas ; 320 caractères au maximum. Absent : `422` `/recipient_email` `required` ; avec `delivery: link` : `not_allowed` ; adresse invalide : `invalid_value`. |

## Réponse

`201 Created` avec le lien et son `url` à usage unique.

```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>"
}
```

## Erreurs

| HTTP | code | Quand |
|---|---|---|
| 403 | `plan_feature_unavailable` | Une intention exige un plan supérieur ; `feature` et `required_plan` la désignent. |
| 404 | `not_found` | L'organisation n'existe pas dans cet environnement. |
| 409 | `organization_disabled` | L'organisation n'est pas active (désactivée) ; aucun lien n'est créé. |
| 409 | `organization_pending_deletion` | L'organisation est programmée pour suppression ; aucun lien n'est créé. |
| 422 | `validation_failed` | Un champ est absent ou invalide ; `/return_url` `origin_not_registered` lorsque son origine n'est pas celle d'une application enregistrée ; `/recipient_email` `required`, `not_allowed` ou `invalid_value`. |
| 401 | `unauthenticated` | L'identifiant est absent, invalide ou révoqué. |
| 403 | `insufficient_permissions` | L'identifiant ne dispose pas de la permission requise. |
| 429 | `rate_limited` | La limite de débit a été dépassée ; réessayez après le délai `Retry-After`. |
| 503 | `service_unavailable` | Une dépendance est temporairement indisponible, ou (`delivery: email`) l'e-mail d'invitation ne peut pas être envoyé ; rien n'est créé. Réessayez après le délai `Retry-After`. |

## Exemple

```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"}'
```

### Envoyer une invitation par e-mail

```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 réponse ne contient ni `url` ni adresse :

```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"
}
```

Avec 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`.
