The admin API does programmatically what the admin console of an organization does: your directory, your HR software or a script can invite members, keep groups up to date, assign licenses or read usage. It is open to every team workspace; groups, roles, rules and security only apply in a managed organization.
https://chat.realmoon.ai/api/v1/orgs/{org_id}You will find the organization ID (org_id) in the admin console › “API and keys”, on the “Getting started” card, in the “First call” example.
Create an admin keyLink to the Create an admin key section
- Open the admin console › “API and keys”, go to the “Admin keys” card, then click “New key”.
- Enter a “Name” that says who uses it, for example “HR software” (80 characters at most).
- Under “Permissions”, choose “No access”, “Read access” or “Read and write” for each area. Choose at least one permission.
- Choose the “Expiration”: “Never” or “In … days” (730 days at most).
- Optional: under “Allowed addresses”, enter one IP address or range per line, for example
203.0.113.0/(50 at most). Leave it empty to accept any address.24 - Click “Create key”, copy it, then click “I copied it”. The key starts with
moai_adm_and will not be shown again.
The page is visible to the owner, the admins and any role allowed to manage the API. An organization can have up to 25 active keys. The list shows the start of each key (moai_adm_ followed by six characters), who created it, when it expires and when it was last used.
A key’s permissions are fixed once it is created: “Edit” only changes its name and allowed addresses. To grant other permissions, create a new key. “Revoke” stops a key immediately and for good.
Authenticate requestsLink to the Authenticate requests section
Send the key in the Authorization header, after Bearer. All requests go over HTTPS.
Authorization: Bearer moai_adm_…- A key only works on the
/routes of its own organization. With the ID of another organization, the response isapi/ v1/ orgs/ {org_id}/ … 404, just as for an organization that does not exist.not_found - Outside these routes (chat, other APIs), the key is refused:
403.admin_key_scope_forbidden - A personal
sk-…key is refused on these routes:403.personal_api_key_forbidden - When allowed addresses are set, a request from any other address gets
403.admin_key_ip_forbidden
Read the key’s accessLink to the Read the key’s access section
GET /me · no scope required
The recommended first call: it confirms that the key works and returns its effective permissions.
curl https://chat.realmoon.ai/api/v1/orgs/$ORG_ID/me \
-H "Authorization: Bearer $MOONAI_ADMIN_KEY"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']}"}
r = requests.get(f"{BASE}/me", headers=HEADERS, timeout=30)
r.raise_for_status()
me = r.json()
print(me["org"]["name"], sorted(me["permissions"]))const BASE = `https://chat.realmoon.ai/api/v1/orgs/${process.env.ORG_ID}`;
const HEADERS = { Authorization: `Bearer ${process.env.MOONAI_ADMIN_KEY}` };
const r = await fetch(`${BASE}/me`, { headers: HEADERS });
if (!r.ok) throw new Error(await r.text());
const me = await r.json();
console.log(me.org.name, Object.keys(me.permissions));{
"org": {
"id": "8c1f2a4e-5b6d-4e7f-9a0b-1c2d3e4f5a6b",
"name": "Lycée Jean Moulin",
"slug": "lycee-jean-moulin",
"is_personal": false,
"org_kind": "education",
"managed": true,
"policy_version": 14
},
"my_role": "api_key",
"roles": [],
"permissions": {
"members.read": "org",
"members.invite": "org",
"members.suspend": "org",
"members.remove": "org",
"usage.read": "self"
},
"managed_group_ids": [],
"group_ids": [],
"kind": "education",
"managed": true,
"platform_override": false,
"labels_display": { "manager": "Enseignant", "member": "Élève" },
"labels_display_en": { "manager": "Teacher", "member": "Student" },
"my_groups": [],
"role_names": {},
"can_leave": false
}| Field | Description |
|---|---|
org | The organization: ID, name, slug, is_personal, type (org_kind: team, education, company, public_sector or other), managed (managed organization) and policy_version (goes up every time the rules change). |
my_role | api_key for a key. |
permissions | The key’s permissions and their reach: org (the whole organization), managed_groups (the groups its creator manages) or self (only oneself). |
labels_display | The display names of the built-in roles for the organization type, in French, and in English in labels_display_en. |
The other fields describe the person who created the key (joined_at, license, can_leave) or stay empty for a key.
ScopesLink to the Scopes section
Each area of the “Permissions” screen maps to two scopes. A …:read scope only opens reads (GET); a …:write scope also opens the reads of its area.
| Area in the admin console | Read | Write | Routes |
|---|---|---|---|
| “Members” | members:read | members:write | /members, /invitations, /, reading /directory and / |
| “Groups” | groups:read | groups:write | /groups |
| “Roles” | roles:read | roles:write | /roles, / |
| “Organization labels” | labels:read | labels:write | /labels |
| “Rules” | rules:read | rules:write | /policies, /models |
| “Licenses” | licenses:read | licenses:write | /licenses, /contract |
| “Usage” | usage:read | none | /usage |
| “Audit log” | audit:read | none | /audit |
| “Organization settings” | org:read | org:write | /general, /settings, /overview, /alerts, writing /directory |
| “Resources” | resources:read | resources:write | /resources |
| “Security” | security:read | security:write | /domains, /security, actions on a member’s account, reading /sso and /scim-token |
| “Webhooks” | webhooks:read | webhooks:write | /webhooks |
A few routes require a different scope from the one of their area:
| Route | Required scope |
|---|---|
GET /me, GET / | none |
GET /, GET / | members:write (they show addresses and codes) |
GET / | members:write |
GET / | groups:write |
GET / | members:read or members:write |
GET / | members:read (members:write is not enough) |
PATCH /, POST /, …/, …/disable, …/enable | security:write |
PUT / | org:write |
A route called without a suitable scope returns 403 , with required_scopes, the list of scopes that would work.
The creator’s rightsLink to the The creator’s rights section
The scope opens a route; the permission decides the action. On every request, the key gets the permissions of the person who created it, narrowed down to its scopes. A key therefore never has more rights than its creator:
- if its creator can only read the members of the groups they manage, the key can only read those members too, without their email address;
- you can only give a key areas where you have at least one permission (otherwise
403when you create it);role_escalation - an action that would grant more than the key’s permissions is refused:
403, with therole_escalation permissioninvolved.
The key is revoked for good when its creator leaves the organization or loses the permission to manage the API. It is refused while the creator’s account or membership is suspended (403 ).
What a key never doesLink to the What a key never does section
- Delete the organization or transfer its ownership.
- Pay or change billing.
- Create, edit or revoke admin keys.
- Configure single sign-on or the SCIM directory token (
403); it can only read them.owner_only - Make someone an admin, or act on the owner, an admin or its own creator.
- Use the chat or any other Moon AI API.
PaginationLink to the Pagination section
Lists accept two query parameters:
| Parameter | Type | Description |
|---|---|---|
page | integer | Page number, starting at 1. Defaults to 1. |
limit | integer | Items per page, from 1 to 200. Defaults to 50. |
An unreadable value is replaced with the default, and an out-of-range value with the nearest bound. Every list returns:
{ "items": [], "total": 0, "page": 1, "limit": 50 }total counts every item that matches the filters. A list can add fields next to items; they are described on its page.
FormatsLink to the Formats section
- Request bodies are JSON (
Content-Type:), 512 KB at most (otherwiseapplication/ json 413). Only the member import takes a CSV file. - An unknown field in a body is refused:
400, withinvalid_request fieldsnaming the field (messageChamp, unknown field).inconnu. - Dates are integers in Unix seconds (UTC).
- IDs (organization, member, group, created role, label…) are opaque strings; never build them yourself.
- Error messages are in French. Your program should rely on the
codefield, which never changes.
ErrorsLink to the Errors section
Every error has the same shape:
{
"detail": {
"code": "admin_key_scope_missing",
"message": "Cette clé n’a pas la portée nécessaire pour cette route.",
"required_scopes": ["members:write"]
}
}message is a human-readable text in French, meant for a person and subject to change. To handle an error in code, always test code.
| Status | code | Cause |
|---|---|---|
| 400 | invalid_request | Invalid body or parameter; fields lists the fields involved (field, message). |
| 400 | invalid_idempotency_key | The Idempotency-Key header is empty, too long or contains non-visible characters. |
| 401 | invalid_admin_key | Unknown or malformed key. The response carries WWW-Authenticate: . |
| 401 | admin_key_expired | The key has expired. |
| 401 | admin_key_revoked | The key was revoked, by hand or because its creator left the organization or lost the permission to manage the API. |
| 403 | admin_key_scope_missing | Missing scope; required_scopes lists the ones that would work. |
| 403 | admin_key_scope_forbidden | The route is outside the organization’s admin routes. |
| 403 | admin_key_ip_forbidden | The IP address is not among the key’s allowed addresses. |
| 403 | admin_key_creator_inactive | The account or membership of the key’s creator is suspended. |
| 403 | personal_api_key_forbidden | A personal sk-… key was sent. |
| 403 | admin_api_disabled | The admin API is turned off on the server. |
| 403 | forbidden | The key does not have the permission for this action, or the action targets a protected person (the owner, an admin). |
| 403 | role_escalation | The action would grant more than the key’s permissions; permission says which one. |
| 404 | not_found | Unknown resource, or one that belongs to another organization. |
| 409 | seats_full | No free seat left in the organization. |
| 409 | idempotency_in_progress | A request with the same Idempotency-Key is still running. |
| 413 | invalid_request | The body is too large. |
| 422 | idempotency_key_reused | Same Idempotency-Key with a different body. |
| 429 | rate_limited | Too many requests; wait Retry-After seconds. |
| 503 | org_policy_unavailable | Data temporarily unreadable; try again after Retry-After. |
| 503 | idempotency_unavailable | The Idempotency-Key cannot be checked right now; try again. |
Codes specific to a route are listed on its page.
Rate limitsLink to the Rate limits section
A key gets 600 requests per minute, including 120 writes (anything other than GET). Every response carries three headers:
| Header | Value |
|---|---|
X-RateLimit-Limit | Requests allowed in the minute. |
X-RateLimit-Remaining | Requests left. |
X-RateLimit-Reset | Seconds until the counter resets. |
Beyond that, the response is 429 with Retry-After. After more than 30 attempts with a wrong key within 10 minutes from the same IP address, that address gets 429 until the end of the window.
Tests that make the server contact an outside address (webhook, MCP server, storage) have their own limit: 20 tests in 10 minutes per person (for a key, its creator), then 429 .
Retryable requestsLink to the Retryable requests section
A POST accepts the Idempotency-Key header (1 to 255 visible characters, for example a UUID). After a network failure, send the same request again with the same key: the action only happens once.
- The first response is kept for 24 hours and returned as is, with the
Idempotent-Replayed:header.true - The same key with a different body returns
422.idempotency_key_reused - While the first request is still running, the same key returns
409withidempotency_in_progress Retry-After:.1 401,403,429and5xxresponses, and responses larger than 2 MB, are not kept: a new attempt runs the request again.
An idempotency key is scoped to one admin key: two different admin keys can use the same value without interfering.
OpenAPI descriptionLink to the OpenAPI description section
The full description of the routes is public and needs no authentication:
https://chat.realmoon.ai/api/v1/orgs/openapi.jsonIt follows OpenAPI 3.1 and carries version 2026-10-30. Each operation lists its scopes in x-moon-scopes (null when it is not open to keys), its group (tags) and, for routes that read a body, the schema of that body. The Error schema describes the shape of errors and WebhookEvent the shape of events. The file can be cached for 5 minutes.
Audit logLink to the Audit log section
Every write made with a key is recorded in the organization’s audit log under the key’s name: the actor is api_key:<key and its label is Clé (API key “name”). Read it with GET /audit.
Admin console only routesLink to the Admin console only routes section
These routes exist under / but only answer a session opened in the admin console, never a key (403 , with an empty required_scopes): admin-keys (key management), api-settings, setup (the “Setting up” card), transfer and delete.
Reference by areaLink to the Reference by area section
| Page | Routes |
|---|---|
| Organization and settings | /overview, /alerts, /general, /settings, /directory |
| Members | /members, import, export, bulk operations |
| Invitations and access codes | /invitations, / |
| Member accounts | /, a member leaving, /security |
| Groups | /groups |
| Roles | /roles, / |
| Labels | /labels |
| Rules | /policies |
| Organization models | /models |
| Licenses and contract | /licenses, /contract |
| Resources | /resources |
| Domains | /domains |
| Usage and audit log | /usage, /audit |
| Webhooks | /webhooks |
| Single sign-on | Reading /sso; writing requires the session of the owner or an admin |
| SCIM 2.0 | Reading /scim-token; creating and revoking require the session of the owner or an admin |
See alsoLink to the See also section
- Managed organizations
- Webhooks
- Developer API, to call the models from your own programs