
# Inviter une personne dans une organisation

`POST /v1/invitations` · autorisation `invitations:write`

Crée une invitation pour qu'une adresse e-mail rejoigne l'organisation. Sans `organization_id`, l'invitation est une invitation d'environnement : c'est le lien d'inscription pour le mode d'inscription sur invitation uniquement, elle n'ouvre aucune adhésion et ne peut pas porter de rôles (`role_ids` renvoie `422 validation_failed`), et la réponse contient `organization_id: null`. `delivery` est obligatoire : `email` envoie l'e-mail d'invitation avec le modèle d'invitation de l'environnement (`delivery_status: queued`), le lien ne figure que dans l'e-mail et la réponse ne contient pas de `accept_url` ; `link_only` n'envoie aucun e-mail et renvoie le lien pour que vous le partagiez. Le lien d'acceptation (`accept_url`) n'est renvoyé que dans cette réponse, une seule fois ; aucun point de terminaison ne le renvoie ensuite. La réponse contient `Cache-Control: no-store`, et une réponse rejouée pour la même `Idempotency-Key` omet le lien et contient `secret_redacted: true`. Si l'envoi d'e-mails d'invitation n'est pas configuré pour le déploiement (uniquement dans les configurations hors production), la livraison `email` renvoie `501 not_implemented` et rien n'est enregistré. Si une invitation en attente avec les mêmes rôles existe déjà pour l'organisation et l'adresse e-mail, elle est renvoyée avec un nouveau lien et la réponse est `200`. Une invitation en attente arrivée à expiration mais pas encore traitée est marquée `expired` dans la même requête et une nouvelle invitation est enregistrée (journalisée sous `invitation.expired`). Une invitation `email` en attente avec les mêmes rôles est renvoyée par e-mail : la réponse est `200` avec `delivery: email` et sans `accept_url`. L'écriture est journalisée sous `invitation.created`.

## Requête

| Champ | Type | Obligatoire | Description |
|---|---|---|---|
| `organization_id` | string | non | Identifiant de l'organisation (`org_…`) ; omettez-le pour une invitation d'environnement (sans rôles). |
| `email` | string | oui | Adresse e-mail de la personne invitée, 320 caractères au maximum. |
| `delivery` | string | oui | `email` (lien uniquement dans l'e-mail) ou `link_only` (lien uniquement dans cette réponse). |
| `role_ids` | string[] | non | Rôles accordés à l'acceptation (`rol_…`), 10 au maximum ; rôle de membre par défaut si la liste est vide. |
| `application_id` | string | non | Application dans laquelle atterrit la personne invitée (`app_…`) ; elle doit exister dans cet environnement. |
| `ttl_days` | integer | non | 1 à 30 ; la valeur par défaut est `invitation_ttl_days` de l'environnement (7). |

## Réponse

`201 Created` avec l'invitation et son lien d'acceptation (`200 OK` lorsqu'une invitation en attente a été renvoyée).

```json
{
  "id": "inv_01j9xt9v3w4y8z0a2b4c6d8e0g",
  "organization_id": "org_01j9xk3m7q8r2s4t6v8w0y2z4a",
  "email": "ada@example.com",
  "role_ids": [
    "rol_01j9xr7s1t2w6x8y0z2a4b6c8e"
  ],
  "application_id": null,
  "delivery": "link_only",
  "delivery_status": "not_sent",
  "status": "pending",
  "send_count": 1,
  "last_sent_at": "2026-10-12T09:31:00.123456Z",
  "expires_at": "2026-10-19T09:31:00.123456Z",
  "accepted_user_id": null,
  "accepted_at": null,
  "revoked_at": null,
  "created_at": "2026-10-12T09:31:00.123456Z",
  "updated_at": "2026-10-12T09:31:00.123456Z",
  "version": 1,
  "accept_url": "https://acme.eu.keenauth.app/u/login?invitation=<token>"
}
```

## Erreurs

| HTTP | code | Quand |
|---|---|---|
| 404 | `not_found` | L'organisation ou l'application n'existe pas dans cet environnement. |
| 409 | `invitation_already_pending` | Une invitation en attente avec des rôles différents existe pour cette adresse e-mail. |
| 409 | `membership_already_exists` | L'adresse e-mail appartient déjà à un membre. |
| 409 | `organization_disabled` | L'organisation est désactivée. |
| 422 | `invitation_invalid` | L'invitation ne peut pas être créée avec ces valeurs. |
| 422 | `validation_failed` | Un champ est manquant ou invalide. |
| 429 | `invite_rate_limited` | L'invitation a été renvoyée il y a moins de 10 minutes ou a déjà été renvoyée 5 fois. |
| 501 | `not_implemented` | `delivery` vaut `email` et l'envoi d'e-mails d'invitation n'est pas configuré pour le déploiement. |
| 401 | `unauthenticated` | L'identifiant d'authentification est absent, invalide ou révoqué. |
| 403 | `insufficient_permissions` | L'identifiant d'authentification ne dispose pas de l'autorisation requise. L'attribution d'un rôle privilégié (`is_privileged`) exige aussi `roles:write` ; sans cette autorisation, rien n'est enregistré et `required_permissions` liste les deux autorisations. |
| 429 | `rate_limited` | La limite de débit a été dépassée ; réessayez après la durée indiquée par `Retry-After`. |
| 503 | `service_unavailable` | Une dépendance est temporairement indisponible ; réessayez après la durée indiquée par `Retry-After`. |

## Exemple

```bash
curl -i -X POST "https://api.eu.keenauth.com/v1/invitations" \
  -H "Authorization: Bearer $KEENAUTH_SECRET_KEY" \
  -H "Content-Type: application/json" \
  -d '{"organization_id":"org_01j9xk3m7q8r2s4t6v8w0y2z4a","email":"ada@example.com","delivery":"link_only","role_ids":["rol_01j9xr7s1t2w6x8y0z2a4b6c8e"]}'
```

## Données personnelles

Ce point de terminaison traite des données personnelles : adhésions aux organisations et invitations. [Données personnelles dans les API KeenAuth](/docs/policies/personal-data) décrit ce qui est stocké, la durée de conservation et la manière dont elles sont supprimées. N'envoyez pas de catégories particulières de données personnelles (santé, données biométriques, religion, opinions politiques, orientation sexuelle) dans aucun champ.
