DocsOpen Moon AI
Browse the docs

Licenses and contract

Read the license pools, set the assignment rules and the fallback pool, assign licenses by hand, and read the contract and invoices through the API.

On this page

An organization under contract can have several license pools: each pool has one plan and a number of licenses. Rules place each member in a pool based on their roles, groups or labels; a fallback pool takes the members whose pool is full. An organization without pools has a single implicit pool, workspace, that covers everyone.

All routes live under https://chat.realmoon.ai/api/v1/orgs/{org_id}. Scopes: licenses:read to read, licenses:write to write. What each plan includes is on the Pricing page.

Read licensesLink to the Read licenses section

GET /licenses · licenses:read

JSON
{
  "licensed": true,
  "pools": [
    { "id": "8f2c1a3e-5b7d-4c9f-a1e3-5b7d9f1a3c5e", "plan_id": "<plan id>", "plan_name": "Lune", "plan_slug": "lune", "seats": 3600, "used": 3412, "source": "contract", "contract_id": "<contract id>", "contract_line_id": "<line id>", "name": null, "period_start": 1756684800, "period_end": 1788220800, "is_fallback": true, "virtual": false },
    { "id": "41bd6e8f-0a2c-4e4a-8c6e-0a2c4e6a8c0e", "plan_id": "<plan id>", "plan_name": "Galaxie", "plan_slug": "galaxie", "seats": 400, "used": 398, "source": "contract", "contract_id": "<contract id>", "contract_line_id": "<line id>", "name": null, "period_start": 1756684800, "period_end": 1788220800, "is_fallback": false, "virtual": false }
  ],
  "rules": [
    { "id": "<rule id>", "pool_id": "41bd6e8f-0a2c-4e4a-8c6e-0a2c4e6a8c0e", "subject_type": "group", "subject_id": "<group id>", "subject_name": "Professeurs", "priority": 0 },
    { "id": "<rule id>", "pool_id": "8f2c1a3e-5b7d-4c9f-a1e3-5b7d9f1a3c5e", "subject_type": "default", "subject_id": null, "subject_name": null, "priority": 1000 }
  ],
  "fallback_pool_id": "8f2c1a3e-5b7d-4c9f-a1e3-5b7d9f1a3c5e",
  "members": 3810,
  "unlicensed": 0,
  "pending": 0,
  "reconciling": false,
  "license_version": 57,
  "can_manage": true,
  "manage_permission": "licenses.manage"
}
FieldDescription
licensedfalse: no pools; pools contains only the implicit workspace pool (virtual: true).
seats, usedThe pool’s licenses (null: no limit) and the active members who hold one.
rulesAssignment rules, in the order they apply.
unlicensedActive members left without a license.
pendingActive members not placed yet.
reconcilingThe rules have changed and members are being placed.

GET /licenses/mine (licenses:read) returns the license of the person who created the key: licensed, managed, org_name, pool_id, source, has_license, plan_id, plan_name, plan_slug. source says where the license comes from: manual, import, scim, invite, rule, default, fallback or unplaced. Another member’s license is shown in the member list.

Assignment rulesLink to the Assignment rules section

PUT /licenses/rules · licenses:write

Replaces the whole list of rules, then recalculates the pool of every member whose license comes from the rules.

FieldTypeRequiredDescription
ruleslistyesUp to 200 rules; an empty list removes every rule.
rules[].pool_idstringyesThe target pool.
rules[].subject_typestringyesrole, group, label or default.
rules[].subject_idstringexcept for defaultowner, admin, member, manager, billing or custom:<id> for a role; the ID of a group or a label.

The first rule that matches a member decides; the default rule (only one) always applies last. A group rule only covers the members of the group, not its managers. Licenses chosen by hand, by import, by the directory or by an invitation do not move.

cURL
curl -X PUT https://chat.realmoon.ai/api/v1/orgs/$ORG_ID/licenses/rules \
  -H "Authorization: Bearer $MOONAI_ADMIN_KEY" \
  -H "Content-Type: application/json" \
  -d '{"rules": [
        {"pool_id": "<Galaxie pool>", "subject_type": "group", "subject_id": "<Professeurs group id>"},
        {"pool_id": "<Lune pool>", "subject_type": "default"}
      ]}'
Python
import os
import requests

r = requests.put(
    f"https://chat.realmoon.ai/api/v1/orgs/{os.environ['ORG_ID']}/licenses/rules",
    headers={"Authorization": f"Bearer {os.environ['MOONAI_ADMIN_KEY']}"},
    json={"rules": [
        {"pool_id": os.environ["POOL_TEACHERS"], "subject_type": "group", "subject_id": os.environ["GROUP_TEACHERS"]},
        {"pool_id": os.environ["POOL_DEFAULT"], "subject_type": "default"},
    ]},
    timeout=60,
)
r.raise_for_status()
print(r.json()["changed"], "members changed license")
JavaScript
const r = await fetch(`https://chat.realmoon.ai/api/v1/orgs/${process.env.ORG_ID}/licenses/rules`, {
  method: 'PUT',
  headers: { Authorization: `Bearer ${process.env.MOONAI_ADMIN_KEY}`, 'Content-Type': 'application/json' },
  body: JSON.stringify({
    rules: [
      { pool_id: process.env.POOL_TEACHERS, subject_type: 'group', subject_id: process.env.GROUP_TEACHERS },
      { pool_id: process.env.POOL_DEFAULT, subject_type: 'default' },
    ],
  }),
});
if (!r.ok) throw new Error(await r.text());
console.log((await r.json()).changed);

The response has the same shape as the read, with changed: the number of members whose license changed.

StatuscodeCause
400invalid_requestRule without subject_id, two default rules, two rules on the same subject, unknown pool, group, label or role. field names the field at fault.
409no_license_poolsThe organization has only one pool: every member has the same license.

Fallback poolLink to the Fallback pool section

PUT /licenses/fallback · licenses:write

FieldTypeRequiredDescription
pool_idstringnoThe pool that takes members whose pool is full; null: they stay without a license.

Send at least {}. The response has the same shape as the read, with changed.

Assign by handLink to the Assign by hand section

POST /licenses/assign · licenses:write

FieldTypeRequiredDescription
user_idslistuser_ids or filter1 to 5,000 members.
filterobjectuser_ids or filterActive members who are in group_id, carry label_id, hold the role role and have the license license (a pool ID or none), all at once. {}: every active member.
pool_idstringnoA pool (kept through every recalculation), none (no license), or null (back to the rules).
JSON
{ "filter": { "group_id": "<group id>", "license": "none" }, "pool_id": "41bd6e8f-0a2c-4e4a-8c6e-0a2c4e6a8c0e" }

Response: {"changed": 12, "skipped": ["<id>"], "matched": 13, "fallback": 0, "unplaced": 0}. skipped lists the IDs that are not active members; fallback and unplaced count the members sent to the fallback pool or left without a license when they go back to the rules.

StatuscodeCause
400invalid_requestuser_ids and filter together, neither of them, more than 5,000 members found, unknown pool.
409no_license_poolsThe organization has only one pool.
409pool_fullThe pool does not have enough free licenses; free and requested give the numbers. Nothing is assigned.

A member’s plan (features, limits, models) follows their license within seconds. When a pool becomes full, the license.pool_full webhook event is sent, at most once per day and per pool.

Contract and invoicesLink to the Contract and invoices section

GET /contract · licenses:read

Returns the organization’s contract and its invoices. The key must also have the right to read billing (or its creator must be the organization’s billing contact); otherwise the route returns 404.

contract is null when there is no active or ended contract. Its fields:

FieldDescription
statusactive or ended.
channelinvoice, external or offered.
plan, seats, linesThe main plan, the number of licenses, and one line per plan (plan, seats, price_per_seat_cents, monthly_amount_ht).
period_start, period_end, days_leftContract period and days left.
billing_intervalyear, semester, quarter, month or once.
price_per_seat_cents, monthly_amount_ht, annual_amount_ht, currencyAmounts in cents, excluding VAT: price of one license per month, monthly total, total over twelve months, and the currency.
buyerBuyer: name, siret, vat_number, address, po_number, engagement_number, service_code, public_buyer.
billing_emailsEmail addresses that receive the invoices.
renewalThe signed renewal, or null.

invoices lists the last 100 invoices, most recent first:

FieldDescription
number, kindNumber and type: period (billing period) or amendment.
status, overdueopen, uncollectible, paid or void; overdue: open and past due.
amount_ht, tax_amount, amount_ttc, currencyAmounts in cents.
issued_at, due_at, paid_at, period_start, period_endDates in Unix seconds.
hosted_url, pdf_urlLinks to the invoice, only when it is open or paid.
chorus_proSubmission to Chorus Pro (deposited_at, reference, status), or null.

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.