DocsOpen Moon AI
Browse the docs

Admin API

Create a moai_adm_ admin key, send it and pick its scopes, then follow the rules shared by the whole API: pagination, errors, rate limits and idempotency.

On this page

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.

Text
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

  1. Open the admin console › “API and keys”, go to the “Admin keys” card, then click “New key”.
  2. Enter a “Name” that says who uses it, for example “HR software” (80 characters at most).
  3. Under “Permissions”, choose “No access”, “Read access” or “Read and write” for each area. Choose at least one permission.
  4. Choose the “Expiration”: “Never” or “In … days” (730 days at most).
  5. Optional: under “Allowed addresses”, enter one IP address or range per line, for example 203.0.113.0/24 (50 at most). Leave it empty to accept any address.
  6. 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.

Text
Authorization: Bearer moai_adm_…
  • A key only works on the /api/v1/orgs/{org_id}/… routes of its own organization. With the ID of another organization, the response is 404 not_found, just as for an organization that does not exist.
  • 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
curl https://chat.realmoon.ai/api/v1/orgs/$ORG_ID/me \
  -H "Authorization: Bearer $MOONAI_ADMIN_KEY"
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']}"}

r = requests.get(f"{BASE}/me", headers=HEADERS, timeout=30)
r.raise_for_status()
me = r.json()
print(me["org"]["name"], sorted(me["permissions"]))
JavaScript
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));
JSON
{
  "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
}
FieldDescription
orgThe 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_roleapi_key for a key.
permissionsThe key’s permissions and their reach: org (the whole organization), managed_groups (the groups its creator manages) or self (only oneself).
labels_displayThe 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 consoleReadWriteRoutes
“Members”members:readmembers:write/members, /invitations, /access-codes, reading /directory and /policies/effective/{user_id}
“Groups”groups:readgroups:write/groups
“Roles”roles:readroles:write/roles, /role-assignments
“Organization labels”labels:readlabels:write/labels
“Rules”rules:readrules:write/policies, /models
“Licenses”licenses:readlicenses:write/licenses, /contract
“Usage”usage:readnone/usage
“Audit log”audit:readnone/audit
“Organization settings”org:readorg:write/general, /settings, /overview, /alerts, writing /directory
“Resources”resources:readresources:write/resources
“Security”security:readsecurity:write/domains, /security, actions on a member’s account, reading /sso and /scim-token
“Webhooks”webhooks:readwebhooks:write/webhooks

A few routes require a different scope from the one of their area:

RouteRequired scope
GET /me, GET /me/requirementsnone
GET /invitations, GET /access-codesmembers:write (they show addresses and codes)
GET /members/import/columnsmembers:write
GET /groups/{group_id}/candidatesgroups:write
GET /policies/effective/{user_id}members:read or members:write
GET /directorymembers:read (members:write is not enough)
PATCH /members/{user_id}/account, POST /members/{user_id}/account/reset-2fa, …/password-reset, …/disable, …/enablesecurity:write
PUT /directoryorg:write

A route called without a suitable scope returns 403 admin_key_scope_missing, 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 403 role_escalation when you create it);
  • an action that would grant more than the key’s permissions is refused: 403 role_escalation, with the permission involved.

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 admin_key_creator_inactive).

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 owner_only); it can only read them.
  • 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:

ParameterTypeDescription
pageintegerPage number, starting at 1. Defaults to 1.
limitintegerItems 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:

JSON
{ "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: application/json), 512 KB at most (otherwise 413). Only the member import takes a CSV file.
  • An unknown field in a body is refused: 400 invalid_request, with fields naming the field (message Champ inconnu., unknown field).
  • 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 code field, which never changes.

ErrorsLink to the Errors section

Every error has the same shape:

JSON
{
  "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.

StatuscodeCause
400invalid_requestInvalid body or parameter; fields lists the fields involved (field, message).
400invalid_idempotency_keyThe Idempotency-Key header is empty, too long or contains non-visible characters.
401invalid_admin_keyUnknown or malformed key. The response carries WWW-Authenticate: Bearer.
401admin_key_expiredThe key has expired.
401admin_key_revokedThe key was revoked, by hand or because its creator left the organization or lost the permission to manage the API.
403admin_key_scope_missingMissing scope; required_scopes lists the ones that would work.
403admin_key_scope_forbiddenThe route is outside the organization’s admin routes.
403admin_key_ip_forbiddenThe IP address is not among the key’s allowed addresses.
403admin_key_creator_inactiveThe account or membership of the key’s creator is suspended.
403personal_api_key_forbiddenA personal sk-… key was sent.
403admin_api_disabledThe admin API is turned off on the server.
403forbiddenThe key does not have the permission for this action, or the action targets a protected person (the owner, an admin).
403role_escalationThe action would grant more than the key’s permissions; permission says which one.
404not_foundUnknown resource, or one that belongs to another organization.
409seats_fullNo free seat left in the organization.
409idempotency_in_progressA request with the same Idempotency-Key is still running.
413invalid_requestThe body is too large.
422idempotency_key_reusedSame Idempotency-Key with a different body.
429rate_limitedToo many requests; wait Retry-After seconds.
503org_policy_unavailableData temporarily unreadable; try again after Retry-After.
503idempotency_unavailableThe 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:

HeaderValue
X-RateLimit-LimitRequests allowed in the minute.
X-RateLimit-RemainingRequests left.
X-RateLimit-ResetSeconds until the counter resets.

Beyond that, the response is 429 rate_limited 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 rate_limited.

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: true header.
  • The same key with a different body returns 422 idempotency_key_reused.
  • While the first request is still running, the same key returns 409 idempotency_in_progress with Retry-After: 1.
  • 401, 403, 429 and 5xx responses, 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:

Text
https://chat.realmoon.ai/api/v1/orgs/openapi.json

It 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 id> and its label is Clé d’API « name » (API key “name”). Read it with GET /audit.

Admin console only routesLink to the Admin console only routes section

These routes exist under /api/v1/orgs/{org_id}/ but only answer a session opened in the admin console, never a key (403 admin_key_scope_missing, 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

PageRoutes
Organization and settings/overview, /alerts, /general, /settings, /directory
Members/members, import, export, bulk operations
Invitations and access codes/invitations, /access-codes
Member accounts/members/{user_id}/account, a member leaving, /security
Groups/groups
Roles/roles, /role-assignments
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-onReading /sso; writing requires the session of the owner or an admin
SCIM 2.0Reading /scim-token; creating and revoking require the session of the owner or an admin

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.