DocsOpen Moon AI
Browse the docs

Invitations and access codes

Invite people with their groups, roles, labels and license, read the invitation limits, resend or revoke invitations, and manage access codes.

On this page

All routes live under https://chat.realmoon.ai/api/v1/orgs/{org_id} and require the members:write scope, including for reads. An invitation is valid for 7 days; the person who accepts it joins the organization with everything that was set up for them, and the member.joined webhook event is sent at that point.

A key never grants the admin role: an admin invitation or access code is reserved for the owner of the organization (403 owner_only_admin_role).

The invitation objectLink to the The invitation object section

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": "<group id>", "name": "Seconde 3" }],
  "labels": [],
  "roles": [{ "key": "manager", "name": "Responsable" }],
  "license": null
}

groups, labels, roles and license are what the person will receive when they join. When an invitation is resent, its author becomes the person or key that resent it.

Read the limitsLink to the Read the limits section

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
}
FieldDescription
seats_leftFree seats minus pending invitations; null when there is no cap.
pending, pending_capPending invitations and their cap; null under contract.
dailyInvitations per day: for the workspace, for one sender and for one email address. null under contract.
email_configuredEmail sending is set up; if not, share each invitation link yourself.
max_emailsMaximum number of email addresses per request.

Daily limits count over a rolling 24 hours. For a key, the sender is the person who created it. The same email address receives at most 3 invitations per day from one organization, even under contract.

InviteLink to the Invite section

POST /invitations · members:write

FieldTypeRequiredDescription
emailslistyes1 to 200 email addresses.
base_rolestringnomember (default) or admin (owner only).
role_keyslistnoUp to 20 roles, given on joining; requires roles:write.
group_idslistnoUp to 50 static groups; requires groups:write.
label_idslistnoUp to 50 member labels; requires labels:write.
pool_idstringnoThe person’s license pool; requires licenses:write.

The same roles, groups, labels and pool apply to every email address in the request.

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": ["<group id>"]}'
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
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/<token>" },
    { "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
}

Each email address gets its own line:

statusMeaning
invitedInvitation created.
reinvitedA pending invitation already existed: it gets a new link (the old one stops working) and 7 more days. It does not take an extra seat.
already_memberThe person is already a member, active or suspended.
errorNothing was created for this email address; code and message say why.
codeCause
invalid_emailInvalid email address.
duplicateEmail address listed twice in the request.
seats_fullEvery seat is taken or held by a pending invitation.
invite_pending_limitToo many pending invitations: revoke some or wait until they are accepted.
invite_limit_workspace, invite_limit_sender, invite_limit_recipientDaily limit reached for the workspace, the sender or the email address.
errorThe invitation could not be created.

link is the person’s invitation link: treat it as a secret. email_sent says that email sending is set up, not that the email was received.

Errors for the whole request:

StatuscodeCause
400invalid_requestadmin, owner or member role in role_keys; unknown role or label; a group that is dynamic, comes from the directory or grants the admin role; unknown license pool.
400licenses_unavailablepool_id while the organization has no licenses.
403owner_only_admin_rolebase_role: admin.
403role_escalationA role, group or label grants more than the key’s permissions.
403license_change_forbiddenThe groups, labels or roles grant a different license, with no pool_id and no right over licenses.

List invitationsLink to the List invitations section

GET /invitations · members:write

ParameterDescription
qPart of the email address.
statuspending (default) or expired (expired within the last 30 days).
page, limitPagination.

Most recent first. Accepted or revoked invitations are not listed.

Resend an invitationLink to the Resend an invitation section

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

Creates a new link (the old one is disabled), valid for 7 days, with the same groups, roles, labels and license. An expired invitation can be resent if a seat is still free.

JSON
{ "invitation_id": "7e2a4c6b-8d0f-4a1b-9c3d-5e7f9a1b3c5d", "expires_at": 1762944000, "link": "https://chat.realmoon.ai/invite/<token>", "email_sent": true }
StatuscodeCause
404not_foundUnknown, accepted or revoked invitation.
409seats_fullExpired invitation and no free seat left.
429invite_limit_recipientToo many invitations sent to this email address today.

Revoke an invitationLink to the Revoke an invitation section

DELETE /invitations/{invitation_id} · members:write

Response: {"success": true}. The link is disabled and everything set up for the person is deleted. Revoking it a second time returns 404.

Access codesLink to the Access codes section

An access code lets people join the organization without a personal invitation: 8 characters drawn from uppercase letters and digits, without I, O, 0 or 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 }] }

Only usable codes (not revoked, not expired, not used up) are listed, most recent first.

POST /access-codes · members:write

FieldTypeRequiredDescription
rolestringnomember (default) or admin (owner only).
max_usesintegernoFrom 1 to 1,000, or -1 for no limit; 1 by default.
expires_in_daysintegernoFrom 1 to 365; 7 by default.

An empty body {} creates a single-use code valid for 7 days. The response is the new code. A code is revoked automatically when its creator leaves the organization or loses the admin role.

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

Response: {"success": true}, also for a code that is already revoked or expired.

See alsoLink to the See also section

Moon AI

Moon AI in action

0:00

Explore

Screenshots of chat.realmoon.ai taken on October 29, 2026, with fictional data.

Moon AI

Your preferences

Visits counted without cookies.

Our server counts visits without cookies or trackers. Your theme and your answer about ad measurement are remembered on this device.

Ad measurement

With your consent, our ad networks’ trackers (Google) measure our ads, from the visit to the sign-up and then to the first subscription and its amount, and may show you our ads. Your answer is valid for 13 months; you can change it here at any time.

No answer saved on this device.

After you decline, no tag is loaded anymore and the site deletes the cookies they had set on realmoon.ai. Those on the ad networks’ own domains are managed with them or in your browser.

The Moon AI app has its own privacy settings.