# Invitations et codes d’accès

> Inviter des personnes avec leurs groupes, rôles, étiquettes et licence, lire les limites d’invitation, renvoyer ou révoquer, et gérer les codes d’accès.

Toutes les routes sont sous `https://chat.realmoon.ai/api/v1/orgs/{org_id}` et demandent la portée `members:write`, lecture comprise. Une invitation est valable 7 jours ; la personne qui l’accepte rejoint l’organisation avec ce qui l’attendait, et l’événement [webhook](https://realmoon.ai/documentation/api-organisations/webhooks/) `member.joined` part alors.

Une clé ne donne jamais le rôle d’administrateur : une invitation ou un code d’accès `admin` est réservé au propriétaire de l’organisation (`403 owner_only_admin_role`).

## L’objet invitation

```json
{
  "id": "7e2a4c6b-8d0f-4a1b-9c3d-5e7f9a1b3c5d",
  "email": "tom.bernard@ecole.fr",
  "role": "member",
  "created_by": "a11f3c5e-7b9d-4f1a-8c2e-4d6f8a0b2c4e",
  "created_by_name": "Camille Martin",
  "created_at": 1762339200,
  "expires_at": 1762944000,
  "expired": false,
  "groups": [{ "id": "<id de groupe>", "name": "Seconde 3" }],
  "labels": [],
  "roles": [{ "key": "manager", "name": "Responsable" }],
  "license": null
}
```

`groups`, `labels`, `roles` et `license` sont ce que la personne recevra à son arrivée. Une invitation renvoyée a pour auteur la personne ou la clé qui l’a renvoyée.

## Lire les limites

`GET /invitations/limits` · `members:write`

```json
{
  "under_contract": false,
  "paid": true,
  "seats_left": 12,
  "pending": 4,
  "pending_cap": 200,
  "daily": { "workspace": 50, "sender": 50, "recipient": 3 },
  "managed": false,
  "email_configured": true,
  "max_emails": 200
}
```

| Champ | Description |
| --- | --- |
| `seats_left` | Sièges libres moins les invitations en attente ; `null` sans plafond. |
| `pending`, `pending_cap` | Invitations en attente et leur plafond ; `null` sous contrat. |
| `daily` | Invitations par jour : pour le workspace, pour un expéditeur et pour une même adresse. `null` sous contrat. |
| `email_configured` | L’envoi d’e-mails est configuré ; sinon, transmettez vous-même le lien de chaque invitation. |
| `max_emails` | Adresses au plus par requête. |

Les limites du jour comptent sur 24 heures glissantes. Pour une clé, l’expéditeur est la personne qui l’a créée. Une même adresse reçoit au plus 3 invitations par jour d’une organisation, même sous contrat.

## Inviter

`POST /invitations` · `members:write`

| Champ | Type | Obligatoire | Description |
| --- | --- | --- | --- |
| `emails` | liste | oui | 1 à 200 adresses. |
| `base_role` | chaîne | non | `member` (par défaut) ou `admin` (propriétaire seulement). |
| `role_keys` | liste | non | 20 rôles au plus, donnés à l’arrivée ; demande `roles:write`. |
| `group_ids` | liste | non | 50 groupes statiques au plus ; demande `groups:write`. |
| `label_ids` | liste | non | 50 étiquettes de membre au plus ; demande `labels:write`. |
| `pool_id` | chaîne | non | Lot de licences de la personne ; demande `licenses:write`. |

Les mêmes rôles, groupes, étiquettes et lot valent pour toutes les adresses de la requête.

```bash title="cURL"
curl https://chat.realmoon.ai/api/v1/orgs/$ORG_ID/invitations \
  -H "Authorization: Bearer $MOONAI_ADMIN_KEY" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: rentree-2026-seconde-3" \
  -d '{"emails": ["tom.bernard@ecole.fr", "lea.dupont@ecole.fr"], "group_ids": ["<id de groupe>"]}'
```

```python title="Python"
import os
import requests

BASE = f"https://chat.realmoon.ai/api/v1/orgs/{os.environ['ORG_ID']}"
HEADERS = {
    "Authorization": f"Bearer {os.environ['MOONAI_ADMIN_KEY']}",
    "Idempotency-Key": "rentree-2026-seconde-3",
}

r = requests.post(
    f"{BASE}/invitations",
    headers=HEADERS,
    json={"emails": ["tom.bernard@ecole.fr", "lea.dupont@ecole.fr"], "group_ids": [os.environ["GROUP_ID"]]},
    timeout=60,
)
r.raise_for_status()
for line in r.json()["results"]:
    print(line["email"], line["status"], line["code"])
```

```javascript title="JavaScript"
const BASE = `https://chat.realmoon.ai/api/v1/orgs/${process.env.ORG_ID}`;
const r = await fetch(`${BASE}/invitations`, {
  method: 'POST',
  headers: {
    Authorization: `Bearer ${process.env.MOONAI_ADMIN_KEY}`,
    'Content-Type': 'application/json',
    'Idempotency-Key': 'rentree-2026-seconde-3',
  },
  body: JSON.stringify({ emails: ['tom.bernard@ecole.fr', 'lea.dupont@ecole.fr'], group_ids: [process.env.GROUP_ID] }),
});
if (!r.ok) throw new Error(await r.text());
for (const line of (await r.json()).results) console.log(line.email, line.status, line.code);
```

```json
{
  "results": [
    { "email": "tom.bernard@ecole.fr", "status": "invited", "code": null, "message": null, "invitation_id": "7e2a4c6b-8d0f-4a1b-9c3d-5e7f9a1b3c5d", "expires_at": 1762944000, "link": "https://chat.realmoon.ai/invite/<jeton>" },
    { "email": "lea.dupont@ecole.fr", "status": "already_member", "code": null, "message": null, "invitation_id": null }
  ],
  "summary": { "invited": 1, "reinvited": 0, "already_member": 1, "errors": 0 },
  "email_sent": true
}
```

Chaque adresse a sa ligne :

| `status` | Sens |
| --- | --- |
| `invited` | Invitation créée. |
| `reinvited` | Une invitation en attente existait : nouveau lien (l’ancien ne fonctionne plus) et 7 jours de plus. Elle ne prend pas de siège de plus. |
| `already_member` | La personne est déjà membre, active ou suspendue. |
| `error` | Rien n’a été créé pour cette adresse ; `code` et `message` disent pourquoi. |

| `code` | Cause |
| --- | --- |
| `invalid_email` | Adresse invalide. |
| `duplicate` | Adresse en double dans la requête. |
| `seats_full` | Tous les sièges sont occupés ou réservés par des invitations en attente. |
| `invite_pending_limit` | Trop d’invitations en attente : révoquez-en ou attendez qu’elles soient acceptées. |
| `invite_limit_workspace`, `invite_limit_sender`, `invite_limit_recipient` | Limite du jour atteinte pour le workspace, l’expéditeur ou l’adresse. |
| `error` | L’invitation n’a pas pu être créée. |

`link` est le lien d’invitation de la personne : traitez-le comme un secret. `email_sent` dit que l’envoi d’e-mails est configuré, pas que l’e-mail a été reçu.

Erreurs de la requête entière :

| Statut | `code` | Cause |
| --- | --- | --- |
| 400 | `invalid_request` | Rôle `admin`, `owner` ou `member` dans `role_keys`, rôle ou étiquette inconnus, groupe dynamique, venu de l’annuaire ou donnant le rôle d’administrateur, lot de licences inconnu. |
| 400 | `licenses_unavailable` | `pool_id` alors que l’organisation n’a pas de licences. |
| 403 | `owner_only_admin_role` | `base_role: admin`. |
| 403 | `role_escalation` | Un rôle, groupe ou étiquette donne plus que les permissions de la clé. |
| 403 | `license_change_forbidden` | Les groupes, étiquettes ou rôles donnent une autre licence, sans `pool_id` ni droit sur les licences. |

## Lister les invitations

`GET /invitations` · `members:write`

| Paramètre | Description |
| --- | --- |
| `q` | Partie de l’adresse. |
| `status` | `pending` (par défaut) ou `expired` (expirées depuis 30 jours au plus). |
| `page`, `limit` | [Pagination](https://realmoon.ai/documentation/api-organisations/administration/#pagination). |

Les plus récentes d’abord. Les invitations acceptées ou révoquées n’apparaissent pas.

## Renvoyer une invitation

`POST /invitations/{invitation_id}/resend` · `members:write`

Crée un nouveau lien (l’ancien est désactivé) valable 7 jours, avec les mêmes groupes, rôles, étiquettes et licence. Une invitation expirée se renvoie s’il reste un siège.

```json
{ "invitation_id": "7e2a4c6b-8d0f-4a1b-9c3d-5e7f9a1b3c5d", "expires_at": 1762944000, "link": "https://chat.realmoon.ai/invite/<jeton>", "email_sent": true }
```

| Statut | `code` | Cause |
| --- | --- | --- |
| 404 | `not_found` | Invitation inconnue, acceptée ou révoquée. |
| 409 | `seats_full` | Invitation expirée et plus aucun siège libre. |
| 429 | `invite_limit_recipient` | Trop d’invitations envoyées à cette adresse aujourd’hui. |

## Révoquer une invitation

`DELETE /invitations/{invitation_id}` · `members:write`

Réponse : `{"success": true}`. Le lien est désactivé et ce qui attendait la personne est supprimé. Une seconde révocation répond `404`.

## Codes d’accès

Un code d’accès permet de rejoindre l’organisation sans invitation nominative : 8 caractères parmi les lettres majuscules et les chiffres, sans `I`, `O`, `0` ni `1`.

`GET /access-codes` · `members:write`

```json
{ "items": [{ "id": "c3b2a1f0-9e8d-4c7b-a6f5-e4d3c2b1a0f9", "code": "K7MZ4QPA", "role": "member", "max_uses": 30, "uses_count": 12, "expires_at": 1762944000, "created_at": 1762339200 }] }
```

Seuls les codes utilisables (ni révoqués, ni expirés, ni épuisés) sont listés, les plus récents d’abord.

`POST /access-codes` · `members:write`

| Champ | Type | Obligatoire | Description |
| --- | --- | --- | --- |
| `role` | chaîne | non | `member` (par défaut) ou `admin` (propriétaire seulement). |
| `max_uses` | entier | non | De 1 à 1 000, ou -1 sans limite ; 1 par défaut. |
| `expires_in_days` | entier | non | De 1 à 365 ; 7 par défaut. |

Un corps vide `{}` crée un code d’une utilisation valable 7 jours. La réponse est le code créé. Un code est révoqué automatiquement quand son créateur quitte l’organisation ou perd le rôle d’administrateur.

`DELETE /access-codes/{code_id}` · `members:write`

Réponse : `{"success": true}`, aussi pour un code déjà révoqué ou expiré.

## Voir aussi

- [Membres](https://realmoon.ai/documentation/api-organisations/membres/)
- [Domaines](https://realmoon.ai/documentation/api-organisations/domaines/#comptes), pour inviter les comptes existants d’un domaine
- [Licences](https://realmoon.ai/documentation/api-organisations/licences/)

---

Documentation Moon AI › API des organisations › Invitations et codes d’accès. Page : https://realmoon.ai/documentation/api-organisations/invitations/. Modifiée le 5 novembre 2026.

© 2026 Stellarr Studio SAS. Tous droits réservés. Moon AI, Moon Blur et Stellarr Studio sont des marques déposées à l’INPI.
