Three kinds of resources are managed here: the MCP servers offered to members in the chat, the organization’s own storage for uploaded files, and the sharing of knowledge bases and agents with groups, roles or labels.
All routes are under https://. Scopes: resources:read to read, resources:write to write. The permissions required are those of the key’s creator: manage connectors (MCP servers), manage storage, manage the library (knowledge bases) or publish to the catalog (agents). Creating, turning on and sharing require a managed organization (otherwise 409 ).
The sharing blockLink to the The sharing block section
MCP servers and shares use the same sharing object:
{ "all": false, "group_ids": ["<group id>"], "role_keys": ["admin", "custom:<id>"], "label_ids": ["<label id>"] }| Field | Type | Description |
|---|---|---|
all | boolean | true: the whole workspace. |
group_ids | list | Organization groups, up to 200. |
role_keys | list | Built-in roles (owner, admin, manager, billing, member) or custom roles (custom:<id>), up to 50. |
label_ids | list | Member labels (not sensitivity labels), up to 200. |
A missing or null block means “shared with no one”. An unknown group, role or label returns 404 (messages Groupe , Rôle , Étiquette : group, role or label not found). Errors on these routes can carry a field field (the field at fault) in addition to fields.
MCP serversLink to the MCP servers section
An organization MCP server is offered in the chat to the members it is shared with, when it is turned on and its last test passed (offered: ), and when their rules allow it. An organization can have up to 50 servers. The flow: create, test, then turn on.
The MCP server objectLink to the The MCP server object section
{
"id": "6f1c2a9e-3b7d-4e21-9a0c-5d8e7f6a1b2c",
"name": "CRM interne",
"url": "https://mcp.example-crm.com/mcp?…",
"transport": "streamable_http",
"header_name": "Authorization",
"auth": "token",
"token_configured": true,
"token_last4": "9xQa",
"readable": true,
"oauth_client_configured": false,
"members_connected": null,
"my_oauth_status": null,
"my_oauth_host": null,
"tools": [
{ "name": "search_contacts", "description": "Recherche des contacts par nom.", "read_only": true, "hash": "3f5a9c…" }
],
"tools_pending": [],
"tools_allowed": ["search_contacts"],
"approval": { "search_contacts": "auto" },
"sharing": { "all": false, "group_ids": ["2d4c6e8a-1b3d-4f5a-9c7e-0a2b4c6d8e1f"], "role_keys": ["admin"], "label_ids": [] },
"sensitivity_label_id": null,
"enabled": true,
"status": "ok",
"last_error": null,
"tested_at": 1793440000,
"offered": true,
"created_at": 1793436400,
"updated_at": 1793440000
}| Field | Description |
|---|---|
url | The URL, masked: the query string becomes ?… and long path segments …. The full URL is never returned. |
transport | auto, streamable_http or sse; after a successful test, the detected transport. |
auth | token (fixed token), none or oauth (each member signs in with their own account). |
token_configured, token_last4 | A token is saved; its last 4 characters (for a token of 8 characters or more). The token is never returned. |
readable | The saved connection details can be read. |
members_connected | OAuth server: number of connected members; null otherwise. |
my_oauth_status | OAuth server: none, ok or expired for the caller’s connection (for a key, its creator’s). |
tools | Tools approved at the last test (up to 40): name, description, read_only, hash. |
tools_pending | Tools that are new (kind: "new") or changed (kind: ) since then, set aside until the next test. |
tools_allowed | Tools offered; null: all of them. |
approval | Per tool: ask (ask every time), auto (without asking) or never. |
sensitivity_label_id | Sensitivity label: only the roles in sharing.role_keys (or else the owner and admins) can then use the server. |
status | untested, ok or failed; last_error gives the message of the last failure. |
offered | enabled and status set to ok. |
List MCP serversLink to the List MCP servers section
GET / · resources:read
Parameters: page, limit. Sorted by name. The response also includes max_servers (50).
Add an MCP serverLink to the Add an MCP server section
POST / · resources:write · managed organization
| Field | Type | Required | Description |
|---|---|---|---|
name | string | yes | 1 to 80 characters, unique in the organization (case-insensitive). |
url | string | yes | An http(s) URL without credentials, 8 to 2,048 characters. It must be public: this is checked during the test. |
transport | string | no | auto (default), streamable_http or sse. |
auth | string | no | token (default), none or oauth. |
header_name | string | no | Header for the token, Authorization by default (letters, digits and hyphens, up to 64). On Authorization, a token without a space is sent as Bearer . |
token | string | if auth is token | The access token, up to 8,192 characters. Ignored for none and oauth. |
tools_allowed | list | no | Tools offered (up to 200 names); missing: all of them. |
approval | object | no | {"<tool>": , with ask, auto or never, up to 200 entries. |
sharing | object | no | Sharing block; missing: no one. |
sensitivity_label_id | string | no | A sensitivity label of the organization. |
The server is created turned off and untested.
curl https://chat.realmoon.ai/api/v1/orgs/$ORG_ID/resources/mcp \
-H "Authorization: Bearer $MOONAI_ADMIN_KEY" \
-H "Content-Type: application/json" \
-d '{
"name": "CRM interne",
"url": "https://mcp.example-crm.com/mcp",
"auth": "token",
"token": "<server token>",
"sharing": {"all": true}
}'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']}"}
server = requests.post(
f"{BASE}/resources/mcp",
headers=HEADERS,
json={
"name": "CRM interne",
"url": "https://mcp.example-crm.com/mcp",
"auth": "token",
"token": os.environ["CRM_MCP_TOKEN"],
"sharing": {"all": True},
},
timeout=30,
).json()
test = requests.post(f"{BASE}/resources/mcp/{server['id']}/test", headers=HEADERS, timeout=60)
test.raise_for_status()
requests.patch(f"{BASE}/resources/mcp/{server['id']}", headers=HEADERS, json={"enabled": True}, timeout=30).raise_for_status()const BASE = `https://chat.realmoon.ai/api/v1/orgs/${process.env.ORG_ID}`;
const HEADERS = { Authorization: `Bearer ${process.env.MOONAI_ADMIN_KEY}`, 'Content-Type': 'application/json' };
const server = await (await fetch(`${BASE}/resources/mcp`, {
method: 'POST',
headers: HEADERS,
body: JSON.stringify({
name: 'CRM interne',
url: 'https://mcp.example-crm.com/mcp',
auth: 'token',
token: process.env.CRM_MCP_TOKEN,
sharing: { all: true },
}),
})).json();
const test = await fetch(`${BASE}/resources/mcp/${server.id}/test`, { method: 'POST', headers: HEADERS });
if (!test.ok) throw new Error(await test.text());
await fetch(`${BASE}/resources/mcp/${server.id}`, { method: 'PATCH', headers: HEADERS, body: JSON.stringify({ enabled: true }) });| Status | code | Cause |
|---|---|---|
| 400 | invalid_request | Empty name, invalid URL, header refused, token missing, invalid tool name, more than 50 servers, label that is not a sensitivity label. |
| 409 | invalid_request | Un (a server already has this name) |
| 409 | org_not_managed | The organization is not managed. |
Read an MCP serverLink to the Read an MCP server section
GET / · resources:read
Returns the server object. 404 for an unknown server.
Update an MCP serverLink to the Update an MCP server section
PATCH / · resources:write
Same fields as on creation, all optional, plus enabled (boolean). Only the fields you send change.
token: a string replaces the token,""deletes it.- Changing the URL,
auth,transport,header_nameor the token resetsstatustountested: the server is no longer offered until the next successful test. Changing the URL,authortransportalso deletes the members’ personal connections. sharing:removes sharing. For an OAuth server, removing a member from the sharing deletes their connection.null enabled:requires a managed organization and a successful last test, otherwisetrue 409(messagetest_required Testez, test the server before turning it on).le serveur avant de l’activer. enabled:is always allowed.false
Delete an MCP serverLink to the Delete an MCP server section
DELETE / · resources:write
Deletes the server, its call log, the members’ connections and its label. Response: {"success": .
Test an MCP serverLink to the Test an MCP server section
POST / · resources:write · managed organization
Connects to the server, reads its tools, approves them and returns the server object with status: "ok". The test does not turn the server on. An OAuth server is tested with the caller’s connection (for a key, its creator’s).
Each test counts toward the limit of 20 tests in 10 minutes per person, shared with storage and webhook tests (429 ).
| Status | code | Cause |
|---|---|---|
| 400 | org_mcp_test_failed | The connection failed; message says why, in French (URL not public, access refused, no server at this URL, timeout…). The server moves to failed. |
| 400 | oauth_sign_in_required | OAuth server: the caller has no valid connection to this server. |
| 400 | oauth_required | An auth: "none" server that requires sign-in: set auth to oauth. Carries host and server_host. |
Connect an account to an OAuth serverLink to the Connect an account to an OAuth server section
POST / · resources:write
Starts the caller’s personal OAuth connection and returns authorize_url, host and server_host. The flow finishes in the browser that started it: use “Test with my account” in the admin console rather than calling this route from a server. Optional body: client_id (512 characters) and client_secret (1,024), for a provider that requires a client ID.
Read the call logLink to the Read the call log section
GET / · resources:read
Who called which tool, when, and with what result; never the arguments or the content. The key’s creator must be able to manage connectors or read the audit log (the key then needs audit:read).
| Parameter | Type | Description |
|---|---|---|
user_id | string | Only this member’s calls. |
page, limit | integer | Pagination. Most recent first. |
{
"items": [
{
"id": "c0a1e2f3-4b5c-4d6e-8f70-91a2b3c4d5e6",
"server_id": "6f1c2a9e-3b7d-4e21-9a0c-5d8e7f6a1b2c",
"user_id": "a1b2c3d4-e5f6-4a7b-8c9d-0e1f2a3b4c5d",
"user_name": "Camille Martin",
"tool": "search_contacts",
"outcome": "ok",
"approved": false,
"duration_ms": 842,
"created_at": 1793441200
}
],
"total": 1,
"page": 1,
"limit": 50
}outcome is ok, error or refused; approved is true for a call made after the member approved it.
Organization storageLink to the Organization storage section
Files uploaded by members can be kept in the organization’s own storage: Amazon S3 or compatible, Azure Blob Storage or Google Cloud Storage. Credentials are encrypted and never returned.
The storage objectLink to the The storage object section
Without storage, the response is {"configured": . Otherwise:
{
"configured": true,
"provider": "s3",
"config": { "prefix": "moonai", "bucket": "acme-moonai-files", "region": "eu-west-3", "endpoint_url": null, "addressing_style": "auto" },
"credentials": { "configured": true, "hint": "7QXZ" },
"enabled": true,
"status": "ok",
"last_error": null,
"tested_at": 1793440000,
"active": true,
"files": 1240,
"bytes": 5368709120,
"pending_deletes": 0,
"last_ok_at": 1793448000,
"last_failure_at": null,
"last_failure_code": null,
"updated_at": 1793440000
}| Field | Description |
|---|---|
credentials.hint | To recognize the credentials: last 4 characters of the access key (S3) or of the account key (Azure), address of the service account (Google). |
status | untested, ok or failed; last_error gives the code of the last failed test. |
active | enabled and status set to ok: new files go there. |
files, bytes | Files kept in this storage and their total size. |
pending_deletes | Objects waiting to be deleted. |
Read the storageLink to the Read the storage section
GET / · resources:read
Configure the storageLink to the Configure the storage section
PUT / · resources:write · managed organization for a creation
| Field | Type | Required | Description |
|---|---|---|---|
provider | string | yes | s3, azure or gcs. |
config | object | yes | Depends on the provider (below). |
credentials | object | on creation, or to change provider or target | Missing: the saved credentials are kept. |
enabled | boolean | no | false by default: a PUT without enabled turns the storage off. |
| Provider | config | credentials |
|---|---|---|
s3 | bucket, region, endpoint_url (https://, public; omit for AWS), addressing_style (auto, path or virtual), prefix | access_key_id, secret_access_key |
azure | account_url (https://), container, prefix | account_key |
gcs | bucket, prefix | service_account: the JSON key of a Google service account, as an object or as text |
prefix is a folder in the bucket (letters, digits, ., _, -, /, up to 200 characters).
A change of provider, configuration or credentials resets the storage to untested and turns it off: the response then carries test_required: and disabled_by_change. To turn it on: test it, then send the same configuration again with enabled: .
| Status | code | Cause |
|---|---|---|
| 400 | invalid_request | Invalid configuration; field names the field (bucket, prefix, endpoint_url, credentials…). |
| 409 | storage_in_use | Changing the bucket, container or URL while files are stored there; files gives their number. Change only the credentials, or turn this storage off. |
| 409 | test_required | enabled: without a successful test. |
| 409 | org_not_managed | Creation in an organization that is not managed. |
Test the storageLink to the Test the storage section
POST / · resources:write
Writes, reads back and deletes a small file, then returns the storage object. The test does not turn the storage on. A failure returns 400 with reason:
reason | Cause |
|---|---|
config | The configuration is invalid. |
denied | The service refuses access: check the credentials and their permissions (write, read, delete). |
not_found | Bucket or container not found. |
unreachable | The service does not respond. |
mismatch | The file read back does not match the file written. |
error | Another failure. |
A failure takes the storage out of use for new uploads; files already stored remain readable.
Delete the storageLink to the Delete the storage section
DELETE / · resources:write
Removes the configuration, without deleting any object from the bucket. Refused while files are stored there (409 : turn it off instead) or while objects are waiting to be deleted (409 , with pending: test it first so they get deleted). Response: {"success": .
SharesLink to the Shares section
An organization knowledge base (knowledge) or agent (agent) can be shared with groups, roles or labels, and can receive a sensitivity label. An unknown resource_type returns 404.
The share objectLink to the The share object section
{
"resource_type": "knowledge",
"resource_id": "4f2a8c1e-6b3d-4e5f-9a7b-0c1d2e3f4a5b",
"name": "Procédures RH",
"owner_id": "91b3d5f7-2a4c-4e6f-8b0d-1e3f5a7c9b2d",
"sharing": { "all": false, "group_ids": ["2d4c6e8a-1b3d-4f5a-9c7e-0a2b4c6d8e1f"], "role_keys": [], "label_ids": [] },
"sensitivity_label_id": null,
"updated_at": 1793452000
}List sharesLink to the List shares section
GET / · resources:read
Parameters: resource_type (knowledge or agent, optional), page, limit. Most recently modified first. Each item carries resource_type, resource_id, name (null if the resource no longer exists), sharing, sensitivity_label_id and updated_at.
List what can be sharedLink to the List what can be shared section
GET / · resources:read
Parameters: resource_type (knowledge by default, or agent), q (searches the name, 128 characters), page, limit. Each item: id, name, owner_id, owner_name. The agents listed are those their owner has shared with the workspace.
Read, set and remove a shareLink to the Read, set and remove a share section
GET / · resources:read
PUT / · resources:write · managed organization
| Field | Type | Required | Description |
|---|---|---|---|
sharing | object | no | Sharing block; missing: no one. Replaces the whole share. |
sensitivity_label_id | string | no | Sensitivity label; missing or null: removed. |
For a knowledge base, its access settings are rewritten: all: false removes read access for the whole workspace, even if its owner had opened it; shares with individual people remain. With a sensitivity label, only the roles in role_keys (or else the owner and admins) can read and edit it.
An agent is shared with the workspace from its “Agents” page; here, you only choose the roles that can use a confidential agent.
| Status | code | Cause |
|---|---|---|
| 400 | agent_share_all_unsupported | all: true on an agent. |
| 400 | invalid_request | Groups or labels on an agent. |
| 400 | role_share_needs_sensitivity | Roles without a sensitivity label. |
DELETE / · resources:write
Removes the organization’s share. Response: {"success": ; 404 if there is none.
List possible recipientsLink to the List possible recipients section
GET / · resources:read
| Parameter | Type | Description |
|---|---|---|
kind | string | groups (default), labels (member labels), sensitivity (sensitivity labels) or roles. |
q | string | Searches the name (or a label’s key and value), 128 characters. |
page, limit | integer | Pagination. |
For roles, items gives the custom roles (role_key custom:<id>, name) and, on the first page, builtin gives the five built-in roles with their names according to the type of organization.