# XonaMind > XonaMind runs AI agents. An agent is a prompt + a model + tools + memory + skills, created in the XonaMind panel. Any backend, script or product can talk to an agent over HTTP with an API key. Base URL: https://api.xonamind.app Every JSON response of the Product API is `{ "status": "ok", "data": … }` or `{ "status": "error", "code": "…", "message": "…", "details"?: … }`. Dates are ISO 8601 and amounts are USD numbers. Errors carry a stable `code`: branch on it, not on the message. ## Authentication - Product API and OpenAI-compatible API: `Authorization: Bearer xm_live_…` (an API key of a project, created in the panel). Each key has permissions (`agents:read`, `agents:run`, `conversations:read`, `runs:control`, `files:write`, `memory:read`, `memory:write`, `memory_bases:read`, `memory_bases:write`, `usage:read`, `connections:manage`, `end_users:manage`, `widget:sessions`, `knowledge:read`, `knowledge:write`), an optional list of allowed agents, an optional expiry and a rate limit per minute (headers `X-RateLimit-Limit`, `X-RateLimit-Remaining`, `X-RateLimit-Reset`; 429 `RATE_LIMITED` with `Retry-After`). A missing permission is 403 `PERMISSION_DENIED`; a bad key is 401 `INVALID_API_KEY`. - Widget routes: `Authorization: Widget ` (a short-lived token minted by your backend). - MCP server and dashboard API (`https://api.xonamind.app/v1/admin`): `Authorization: Bearer xm_pat_…` (a personal token of an account, bound to one workspace, scope `read` or `write`) or `xm_oat_…` (the OAuth access token of an MCP client the account authorized in the browser; see "MCP server"). - Call these APIs from your backend. Never put an API key in a browser: use a widget session instead. ## Product API (https://api.xonamind.app/v1) - `GET https://api.xonamind.app/v1/agents` → the agents the key can use: `{ slug, name, description, avatarUrl }[]`. - `POST https://api.xonamind.app/v1/agents/{slug}/messages` with `{ externalUserId, content, conversationId?, attachments?: string[], userContext?: object, toolAuth?: { [mcpServerSlug]: token }, maxCostUsd?, async?: boolean }`. `externalUserId` is YOUR id for the end user (1-200 characters); conversations and memory belong to it. Without `conversationId` a conversation is created. - Default response: Server-Sent Events (`id`, `event`, `data` per event). Events: `run_started`, `text_delta` `{ text }`, `reasoning_delta`, `tool_call_started` `{ callId, name, source, arguments, actions? }`, `tool_call_finished` `{ callId, name, status, result, durationMs, actions? }` (`actions` appears only when the tool is a catalog tool and its arguments follow the shape: `[{ tool, args }]` on started, `[{ tool }]` on finished; `arguments` is unchanged), `image`, `citation`, `confirmation_required` `{ runId, toolCalls: [{ callId, name, arguments, actions? }] }` (`actions` appears only when the pending tool is a catalog tool such as `run_tools { calls: [{ tool, args }] }`, a batch `{ tools: [{ tool_slug, arguments }] }` or a single `{ action, params }` (the shape is detected from the tool's input schema): it lists ALL the inner calls as `{ tool, args, confirm }`, with `confirm: true` on the ones that asked for the approval, so you can show 'generate_image (...)' instead of 'run_tools'; approving runs every inner call), `usage` (also `cachedTokens` and `cacheWriteTokens`: prompt-cache reads and writes, already inside `promptTokens`), `done` `{ runId, messageId, status, stopReason, usage, output }`, `error` `{ code, message }`. A `ping` event every 15 s is to be ignored. The stream ends after `done`, `error` or `confirmation_required`. - `Accept: application/json` → waits for the end and returns `{ runId, conversationId, status, stopReason, message, pendingConfirmation, usage, error }`. - `"async": true` → `202 { runId, conversationId, status }` right away; read the result with `GET https://api.xonamind.app/v1/runs/{id}`. - `POST https://api.xonamind.app/v1/conversations/{id}/messages` with `{ content, attachments?, userContext?, toolAuth?, maxCostUsd?, async? }`. Only one run can be alive per conversation (409 `RUN_IN_PROGRESS`). - `POST https://api.xonamind.app/v1/conversations` `{ agent, externalUserId, title?, metadata? }`, `GET https://api.xonamind.app/v1/conversations?externalUserId=&agent=&cursor=&limit=`, `GET https://api.xonamind.app/v1/conversations/{id}` → `{ conversation, messages }`. - `GET https://api.xonamind.app/v1/runs/{id}` → status, usage (`promptTokens`, `completionTokens`, `reasoningTokens`, `costUsd`), tool calls and `output` `{ text, data?, error? }`. `GET https://api.xonamind.app/v1/runs/{id}/events?after={seq}` re-attaches to the SSE of a run. - `POST https://api.xonamind.app/v1/runs/{id}/confirm` `{ decision: "approve" | "reject", callIds: string[] }`: a tool that needs approval stops the run in `waiting_confirmation` and emits `confirmation_required`. `callIds` must be exactly the pending calls you saw (otherwise 409 `CONFIRMATION_MISMATCH`). `POST https://api.xonamind.app/v1/runs/{id}/cancel` cancels a run. - `POST https://api.xonamind.app/v1/files` (multipart, field `file`, up to 20 MB) → `{ id, name, mime, sizeBytes, kind, url }`; send `id` in `attachments`. - `GET https://api.xonamind.app/v1/memory?externalUserId=`, `DELETE https://api.xonamind.app/v1/memory/{id}`: what the agents remember about an end user. - Memory only (no agents needed; permissions `memory_bases:read` / `memory_bases:write` for the shared bases, `memory:read` / `memory:write` for the memory of an end user): `GET https://api.xonamind.app/v1/memory/bases` lists the shared memory bases; `POST https://api.xonamind.app/v1/memory` `{ base?, content, kind?: fact | preference | rule, tags?, externalUserId? }` saves a memory (with `base` into that shared base; without `base` and with `externalUserId` it is the memory of that end user; with neither, the first shared base); `GET https://api.xonamind.app/v1/memory/search?q=&base=&externalUserId=&tags=&kind=&limit=` is a hybrid (vector + text) search; `GET https://api.xonamind.app/v1/memory?base=&tags=&kind=&curation=&cursor=` lists, newest first (`curation` is `raw` = not reviewed, `reviewed` or `approved`; `kind` can be `gap`); `PATCH https://api.xonamind.app/v1/memory/{id}` edits; `DELETE https://api.xonamind.app/v1/memory/{id}` archives. Secrets, cards and tax ids are rejected (422 `MEMORY_REJECTED`), a near-duplicate is 409 `MEMORY_DUPLICATE`, the plan limit is 402 `PLAN_LIMIT_REACHED`. Embeddings are included for free. Open questions: when an agent searches its memory and finds nothing it declares a question (`memory_gap`, kind `gap`) that never enters recall; `GET https://api.xonamind.app/v1/memory/gaps?base=&limit=` lists them and `POST https://api.xonamind.app/v1/memory/gaps/{id}/answer` `{ content }` (`memory_bases:write`) creates the fact and closes the question (409 `MEMORY_NOT_OPEN_GAP` if it is not open); if a very similar fact already exists nothing new is created: the question is archived pointing to it and the answer is `{ gap, item: , duplicate: true }`). A base holds at most 200 open questions (past that `memory_gap` answers `rejected` with reason `gaps_limit_reached`). Questions an agent files without `base` land in its private base: only the dashboard shows and answers those; `https://api.xonamind.app/v1/memory` and the memory MCP only see the questions of shared bases. A question cannot be edited (PATCH is 422): answer it or archive it. Relations: `POST https://api.xonamind.app/v1/memory/relations` `{ fromId, toId, kind, notes? }` (`memory_bases:write`) relates two memories of the same shared base; with an API key it is saved as a proposal (`status: proposed`, `source: agent`) that a person confirms in the dashboard (409 `MEMORY_RELATION_EXISTS`, 422 `MEMORY_RELATION_INVALID`); `kind` is snake_case text (suggested: depends_on, part_of, example_of, relates_to, derived_from, contradicts, corroborates, supersedes). - Knowledge by API key (`knowledge:write`): `POST https://api.xonamind.app/v1/knowledge/{kbSlug}/documents` (multipart `file`, or JSON `{ url }` or `{ name, text }`) answers 202 with the document in status `pending`; `GET https://api.xonamind.app/v1/knowledge/{kbSlug}/documents/{id}` returns its status (`pending` | `processing` | `ready` | `failed`). - Agent config, on-demand tools (`tools.deferral: { enabled: false, alwaysLoaded: [], minTools: 12 }`): opt-in. With at least `minTools` HTTP/MCP tools (not in `alwaysLoaded`; built-ins, subagents and server tools are never deferred) the model gets only the other tools plus `load_tools`, and a short catalog in the prompt; it loads definitions with `load_tools { query }` (`select:a,b` or keywords, max 5 by search) and they stay available for the rest of the conversation. 422 at `tools.deferral.alwaysLoaded.` if the agent has no such tool. - Agent config, MCP catalog servers (`tools.mcp: [{ server, allow, allowActions? }]`): a server that exposes a catalog tool (`run_tools { calls: [{ tool, args }] }`, `{ tools: [{ tool_slug, arguments }] }` or `{ action, params }`) runs ANY inner action once `run_tools` is in `allow`. `allowActions` (e.g. `["list_*", "generate_image"]`; letters, digits and underscores compared without case, a trailing `*` is a prefix; absent or empty = all) limits them: if any inner call is not allowed, the whole `run_tools` call fails with `ACTION_NOT_ALLOWED: is not allowed for this agent. Allowed: ` without contacting the server or asking for a confirmation (unreadable arguments, non-canonical names and nested batches also fail). A tool of that server without a catalog shape runs only if it declares `readOnlyHint` (like `search_tools`) or its own name is in `allowActions`. 422 at `tools.mcp..allowActions` if the server has no catalog tool in `allow`. - MCP servers that need OAuth (admin API, `https://api.xonamind.app/v1/admin/mcp-servers/:id/oauth/*`): create the server with `authMode: "oauth"` (no `oauthProviderId`), then `POST …/oauth/discover` (finds the authorization server from the 401 + resource metadata, registers XonaMind by client ID metadata document or dynamic registration; answers `{ provider: { id, slug, issuer, registration, scopes } }`; 422 `MCP_OAUTH_NOT_SUPPORTED`, `MCP_OAUTH_DISCOVERY_FAILED`, `MCP_OAUTH_REGISTRATION_UNSUPPORTED`), (409 `MCP_OAUTH_WOULD_REPLACE_AUTH` with `details.replaces` if the server has a manual auth: repeat with `{ replace: true }`), then `POST …/oauth/connect { returnTo?, useForAllRuns? (default false) }` (answers `{ authorizeUrl }`: open it and authorize; with `useForAllRuns: true` that account becomes the owner and every run of the server uses its connection). `DELETE …/oauth/connection` disconnects. `GET …/mcp-servers/:id` shows `oauth: { providerId, registration, issuer, scopes, owner, connection }`. A server in oauth mode without discovery fails with `MCP_OAUTH_NOT_DISCOVERED`. - MCP server headers (admin API, `POST/PATCH https://api.xonamind.app/v1/admin/mcp-servers`): besides the credential of `authMode`, `headers: [{ name, value }]` (up to 10) are extra headers sent on every connection (runtime, test, sync). `value` is a template: `{{ credentials.NAME }}` (a vault credential), `{{ user.token }}` (the `toolAuth[slug]` of the request; in test/sync the body `token`) or `{{ user.token | credentials.NAME }}` (the token if sent, else the credential), with optional text around (`Bearer {{ credentials.X }}`). Unknown credential or placeholder: 422 at `headers.N.value`. Without a token and without fallback the tool fails with `MISSING_TOOL_AUTH`. A header named like the `authMode` one wins. Responses show the templates, never resolved values. A fixed value (no variable) is rejected in sensitive header names (authorization, cookie, key, token, secret, password, auth); using `credentials.*` in headers requires the admin role. - Agent config, sealed arguments (`tools.sealedArgs: { "" | ":": { param: value } }`): parameters the system fills in, not the model. The key is a model tool name (`mcp__srv__create_ticket`, an HTTP tool slug) or, for a catalog tool, `:` (same syntax as confirmation rules) to set the parameter inside each matching inner call. Values are strings (up to 400 characters, may use `{{ user.externalId }}` and `{{ user.context. }}`), numbers or booleans; up to 20 tools and 10 parameters each. Parameters are always injected (even if the cached schema does not declare them); a template with no value rejects the call with `SEALED_ARGS_UNSAFE` (fail closed). Sealed values stay in run history and events, and `userContext` is controlled by your product: for identity use `{{ user.externalId }}`. The runtime overrides what the model wrote before confirmations and execution (the approval shows the final arguments) and hides direct sealed parameters from the tool schema. 422 if the tool is not an MCP/HTTP tool of the agent or a placeholder is unknown; a parameter missing from the cached schema is a warning. - Memory bases (admin API, `https://api.xonamind.app/v1/admin/memory-bases`): agent knowledge lives in bases, private to one agent (`ownerAgentId`) or shared and granted per agent (`PUT …/memory-bases/{id}/grants` with `{ grants: [{ agentId, access: read | write }] }`). An agent searches the bases it was granted, plus its private base when its config reads the `agent` scope and the shared `workspace` base when it reads `workspace` (its `memory.scopes` rule those two on every call); whatever an agent writes to a base, its private one included, waits for staff approval. Per-end-user memory has no base (`GET https://api.xonamind.app/v1/admin/memory?baseId=none`). Review: every item has a `curation` (`raw` waiting for review, `reviewed`, `approved`); `POST https://api.xonamind.app/v1/admin/memory/bulk-curate` `{ ids, curation }` reviews up to 200 at once, `GET https://api.xonamind.app/v1/admin/memory-bases/{id}/health` returns `curatedPct`, `rawCount`, `gapsOpen` and `islands`, and `GET https://api.xonamind.app/v1/admin/agents/{id}/memory/preload-preview` shows which rules fit in the prompt budget (`memory.rulesPreload.maxChars`, default 8000). Graph and dreaming: relations between memories of one base live in `https://api.xonamind.app/v1/admin/memory/relations` (`POST` creates a confirmed one; `PATCH /{id}` `{ status: confirmed | rejected }`; `DELETE /{id}`); areas are `area:` tags with a hub memory (never recalled or counted); `GET https://api.xonamind.app/v1/admin/memory-bases/{id}/graph` returns `{ nodes, edges, areas }`; `POST …/{id}/reset-structure` `{ relations: proposed | all, areas: boolean, area? }` undoes what the dreaming built. Each base is dreamed on its own (duplicates, contradictions, relations, areas, insights that wait for review, expiry), at most every hour and only when there is work (the pace halves after passes that change nothing, up to 8 hours); `POST …/{id}/dream` (admin) runs a pass now (202; 409 `MEMORY_DREAM_RUNNING`, 422 `MEMORY_BASE_NOT_CONSOLIDATABLE`) and `GET …/{id}/dreaming/runs` lists the last 30 with stages, time and cost. The agents' `memory_search` adds up to 3 memories linked by a confirmed relation to its top results (`expansion: true`). - `GET https://api.xonamind.app/v1/knowledge/{kbSlug}/search?q=&topK=`: hybrid search in a knowledge base. - `GET https://api.xonamind.app/v1/usage?from=&to=&externalUserId=&keySource=`: tokens and cost (USD) of the project, by agent and by kind; `totals` carry `costUsd` (= `providerCostUsd`, the real cost) and `billedUsd` (what was charged in credits). Bill your own users with the `usage.costUsd` of each run: it is always the real cost, whoever paid the provider. The run `usage` also has `providerCostUsd`, `billedUsd` and `keySource` (`workspace` | `platform` | `included`). - Common errors: 401 `INVALID_API_KEY`, 402 `SPEND_LIMIT_EXCEEDED` / `INSUFFICIENT_CREDITS` / `NO_PROVIDER_KEY`, 424 `PROVIDER_KEY_INVALID`, 404 `AGENT_NOT_FOUND`, 409 `RUN_IN_PROGRESS`, 422 `VALIDATION_ERROR` (`details.fields`), 429 `RATE_LIMITED`. ## Provider keys, credits and margin A model runs with ONE of two keys, chosen per provider for each call: - **Your own provider key** (bring your own key): save it in the dashboard (Providers and models → Your keys) or with `PUT https://api.xonamind.app/v1/admin/provider-keys/{providerSlug}` `{ apiKey }` (owner/admin). The key is checked with a cheap call to the provider and stored encrypted; it is never returned again (only `last4`, `status`: `active` | `invalid` | `unverified`). The provider bills you directly, so the run is not charged in credits (`billedUsd = 0`, `keySource = "workspace"`). `GET` lists the providers and your keys, `POST …/{providerSlug}/test` checks the key again and `DELETE` removes it. 422 `PROVIDER_KEY_REJECTED` when the provider refuses the key (nothing is saved). - **The XonaMind key**, paid with credits: only if the provider allows it, the workspace has it on (`PATCH https://api.xonamind.app/v1/admin/workspace` `{ platformKeysEnabled }`) and, when the plan charges usage, the balance is above zero. Each call is charged `real cost × (1 + margin / 100)` (`billedUsd`; the margin is `platformMarginPct` of the workspace). `costUsd` / `providerCostUsd` stay the real cost. Embeddings (memory and knowledge) always use the XonaMind key and are included for free (`keySource = "included"`). Order, per model: if you have an own key that can serve a model, ONLY your own key is used for that model, whatever fails (auth, rate limit, 5xx, quota): the XonaMind key (credits) is used for a model only when none of your keys can serve it. Each fallback model of an agent follows the same rule on its own. Errors: 402 `NO_PROVIDER_KEY` (`details: { model, providers, platformAllowed }`) when no key can serve the model (`platformAllowed: true` means adding credits fixes it, `false` means a key of your own is needed); 424 `PROVIDER_KEY_INVALID` (`details.provider`) when the provider rejects your own key (the key is marked `invalid`) and no other own key can finish the run; any other provider error of your own key (429, 5xx, quota) is returned as usual. The run never falls back to the XonaMind key silently; 402 `INSUFFICIENT_CREDITS` when the balance cannot cover the maximum cost of a run on the XonaMind key. `GET https://api.xonamind.app/v1/admin/models` (with `X-Workspace-Id`) marks each model and offering with `access`: `own_key` | `platform` (with `marginPct`) | `unavailable`. Plans are limits only (every plan has every module): `agents`, `members`, `knowledgeBases`, `knowledgeDocuments`, `httpTools`, `webhooks`, `schedules`, `runsPerMonth`, `personalTokens`, `mcpServers`, `projects`, `skills`, `memoryItems` (active memories) and `memoryBases` (shared memory bases; the private base of an agent never counts); `null` is unlimited. Creating past a limit is 402 `PLAN_LIMIT_REACHED` (`details.limit`). ## OpenAI-compatible API (https://api.xonamind.app/openai/v1) Use any OpenAI SDK with `baseURL = https://api.xonamind.app/openai/v1` and an XonaMind API key (permission `agents:run`). The "model" is an agent slug. - `GET https://api.xonamind.app/openai/v1/models` → the agents the key can use, as `{ object: "list", data: [{ id: "", object: "model", created, owned_by }] }`. - `POST https://api.xonamind.app/openai/v1/chat/completions` with `{ model: "", messages, stream?, user?, metadata?, response_format? }`. - `user` is the end user id (default `openai:anon`). - Continue a conversation with the header `X-Conversation-Id` (or `metadata.conversation_id`); every response returns `X-Conversation-Id`. Without it a new conversation is created and the earlier `user` / `assistant` messages of the request become its history; `system` messages are added as instructions of the session. The last `user` message is the one answered. - `stream: true` → `chat.completion.chunk` events and `data: [DONE]`; the last chunk carries `finish_reason` and `usage`. - The tools of the agent run inside XonaMind and are never exposed; `tools`, `temperature` and `max_tokens` of the request are ignored (the agent config rules). A run that needs the approval of a tool ends with `finish_reason: "tool_calls"` and a message that says so; approve it with the Product API. - `response_format` (`json_object` or `json_schema`) is honored when the agent does not set its own output. - Errors use the OpenAI format `{ "error": { "message", "type", "code", "param" } }`: 401 `invalid_api_key`, 402 `insufficient_quota`, 404 `model_not_found`, 429 `rate_limit_exceeded`. ## Chat widget 1. Your backend asks for a session: `POST https://api.xonamind.app/v1/widget/sessions` (API key with `widget:sessions`) with `{ agent, externalUserId, userContext?, subject?: { type, id }, ttlSeconds? }` (60-86400, default 3600; `userContext` up to 4 KB) → `{ token, expiresAt }`. 2. Your page loads the script and mounts it: `` or `XonaMindWidget.mount({ token, container?, theme?, locale?, title?, onTokenExpired })`; `onTokenExpired` must return a new token. 3. The widget talks to `https://api.xonamind.app/v1/widget/*` with `Authorization: Widget `: `GET /conversation`, `POST /messages` (SSE, same events as above), `POST /runs/{id}/confirm`, `POST /conversation/reset`, `POST /files`. CORS is limited to the project's allowed origins (set them in the panel). Errors: 401 `WIDGET_TOKEN_INVALID`, 401 `WIDGET_TOKEN_EXPIRED`, 403 `ORIGIN_NOT_ALLOWED`. ## Outgoing webhooks Create endpoints in the panel (Webhooks). XonaMind sends `POST ` with `Content-Type: application/json` and the headers `XonaMind-Event: `, `XonaMind-Delivery: ` and `XonaMind-Signature: t=,v1=`. The body is `{ "id": "", "type": "run.succeeded", "createdAt": "…", "workspaceId": "…", "data": { … } }`. Verify the signature: `v1 = HMAC-SHA256(secret, ".")` in hex, with the `whsec_…` secret of the endpoint, comparing in constant time and rejecting an old `t`. Any 2xx within 10 s is a success; anything else is retried (1 min, 5 min, 30 min, 2 h, 12 h). Run events carry `{ runId, conversationId, agent, projectId, externalUserId, status, stopReason, output, usage }`. ## Inbound webhooks An inbound webhook is a public URL (`POST https://api.xonamind.app/v1/hooks/{id}/{token}`, created in the panel; the token is shown once) that other systems call with a JSON body (up to 256 KB). It renders the hook templates (`{{ payload.x }}`, `{{ headers.x }}`) and runs the agent. No API key is needed; an optional HMAC signature is verified over the raw body. Every response is `{ status: "ok", data }`: - Async hooks (default): 202 `{ status: "started", runId }`. - Sync hooks (`responseMode: sync`, waits up to `syncTimeoutSeconds`, 1 to 60, default 25): 200 `{ status: "succeeded" | "failed" | "cancelled", runId, output, error? }` when the run ends (a failed run is a 200 with `status: "failed"` and `error: { code, message }`; `output` is the final text, or the JSON of a structured output), 200 `{ status: "waiting_confirmation", runId }`, or 202 `{ status: "running", runId }` on timeout (the run keeps going; read it from the admin API). A sync hook with a busy `per_key` conversation is 409 `RUN_IN_PROGRESS`. - `per_key` async hooks never lose an event to a busy conversation: while a run is alive (or events are already waiting) the event is stored and answered 202 `{ status: "queued" }`; when the conversation is free the waiting events of that key run as ONE turn (their messages joined with a blank line, in arrival order). With `debounce: { enabled, idleSeconds (1-600, default 20), maxSeconds (1-3600, default 90) }` every event is answered 202 `{ status: "buffered" }`, each one restarts the idle wait (never past `maxSeconds` from the first), and the buffer runs as one turn when it expires. While the previous turn of the conversation is alive (also while it waits for a confirmation) the events keep waiting; events older than 24 h are dropped. A key can hold 50 waiting events or 256 KB of text at most (429 `HOOK_QUEUE_FULL`; 503 `HOOK_QUEUE_BUSY` with `Retry-After: 1` under contention). - Errors: 404 `HOOK_NOT_FOUND`, 401 `INVALID_SIGNATURE`, 400 `INVALID_JSON`, 413 `PAYLOAD_TOO_LARGE`, 429 `RATE_LIMITED` (60 per minute per hook). ## Connections (accounts of the end user) Agents can use tools that act on a third-party account of the end user (Google, GitHub…). Your backend starts the connection with `POST https://api.xonamind.app/v1/connections/{provider}/authorize` `{ externalUserId, redirectUrl }` (permission `connections:manage`; `redirectUrl` must be inside the project's allowed redirect origins) → `{ url, expiresAt }`: send the user to `url`; they come back to `redirectUrl` with `?connection=ok|error&provider=`. `GET https://api.xonamind.app/v1/connections?externalUserId=` lists them and `DELETE https://api.xonamind.app/v1/connections/{id}` removes one. When a tool needs an account that is not connected the stream emits `connection_required`. ## MCP server (the whole workspace) Manage everything the dashboard manages from Claude, ChatGPT, Cursor or any MCP client: `https://api.xonamind.app/mcp` (Streamable HTTP, stateless), as a custom connector with just the URL (OAuth) or with `Authorization: Bearer xm_pat_…`. - OAuth (no token to paste): in Claude.ai / Claude Desktop (Settings → Connectors → Add custom connector), ChatGPT (connectors / developer mode), Cursor or VS Code, add a remote MCP server with ONLY the URL `https://api.xonamind.app/mcp`. The client finds the login by itself (the 401 carries `WWW-Authenticate: Bearer resource_metadata="https://api.xonamind.app/.well-known/oauth-protected-resource/mcp"`), you sign in to the dashboard, choose the workspace and the access (`mcp:read` reads, `mcp:write` also changes things with your role, `mcp:platform` only for superadmins) and approve. The project gets an access token `xm_oat_…` (1 hour) and a rotating refresh token `xm_ort_…` (30 days) and behaves EXACTLY like a personal token of that scope: same tools, roles, denylist, per-minute cap and audit log (`via: "oauth"` with the project name). See and disconnect your projects with `GET`/`DELETE https://api.xonamind.app/v1/admin/account/connections`. Discovery: `https://api.xonamind.app/.well-known/oauth-protected-resource` and `https://api.xonamind.app/.well-known/oauth-authorization-server` (PKCE S256, dynamic project registration at `https://api.xonamind.app/oauth/register`). - Personal token: create it in the dashboard (Account → Tokens) for ONE workspace with a scope: `write` (default; everything your role allows) or `read` (only reads; the write actions are not even in the catalog). It acts with the role your account has TODAY in that workspace (a superadmin acts as owner). The workspace is the token one: there is no workspace argument. A token never signs in, changes the password, the email or the 2FA, lists or closes sessions, creates or revokes tokens, accepts an invitation, transfers the ownership or archives the workspace (403 `SESSION_REQUIRED`; those need the signed-in dashboard). Platform operations (`superadmin_*`, models, providers) need a token created with `platformAdmin` by a superadmin account (403 `PLATFORM_TOKEN_REQUIRED` otherwise); they are in the catalog only for such tokens. JSON-RPC batches are rejected (-32600): send one request per call (every call of a `run_tools` counts for the per-minute cap of the token: a batch of N calls costs N, and it is rejected whole, running nothing, when fewer than N are left). - Tools: the server ALWAYS lists exactly two, `search_tools` and `run_tools` (there is no mode that lists every action). `search_tools` finds actions: words in English or Spanish (`{ "query": "create a scheduled task" }`), an empty query for the map of areas with every action name, `select:name1,name2` for those actions with their `input_schema`, `area:` for a short list of an area and `guide:` for a guide (agents, automation, tools, memory, knowledge). `run_tools` runs up to 20 actions found with it: `{ "calls": [{ "tool": "agents_list", "args": {} }], "parallel": false }`; they run in order and stop at the first error (the rest come back `skipped`), or all at once with `parallel: true`; one call returns its result as is, several come back as `[n] name: ok|error|skipped` blocks. Arguments are validated against the `input_schema` before running. - Actions: one per operation of the dashboard API, named `__` (for example `agents_list`, `agents_revisions_create`, `skills_create`, `knowledge_documents_add`, `tools_http_create`, `automation_schedules_create`, `quality_evals_runs_start`, `workspace_members_list`, `workspace_usage_get`). They run the same operation as `https://api.xonamind.app/v1/admin/*`: same validation, roles, audit log and errors. The input comes from the route (path ids, query and body fields); an error comes back as `CODE (HTTP status): message {details}` (for example `ROLE_FORBIDDEN (HTTP 403): … {"required":"admin"}`, `VALIDATION_ERROR (HTTP 422)` with the fields, `TOKEN_SCOPE_FORBIDDEN`). Superadmin actions appear only for superadmin accounts. - Curated actions (by slug, shorter): `list_agents`, `get_agent`, `create_agent`, `update_agent`, `run_agent`, `search_memory`, `add_memory`, `search_knowledge`, `list_conversations`, `get_conversation`, `get_usage`. Writes need the member role. - Memory-only MCP server: `https://api.xonamind.app/mcp/memory` exposes ONE tool `memory` with `{ action, params }` (actions `bases_list`, `list` with a semantic `q` and filters `base`, `tags`, `kind`, `get`, `create`, `update`, `delete`, `bulk_create` up to 50, `health`, `gaps_list`, `gap_answer`, `graph`, `relate`; `list` also takes `curation`; `relate` with an API key is a proposal, with an account it is confirmed) over the shared memory bases, to use XonaMind as the shared memory of a team from Claude Code, Cursor or Codex. Auth: an OAuth access token issued for the resource `https://api.xonamind.app/mcp/memory` (scopes `memory:read` | `memory:write` | `memory:admin`; discovery at `https://api.xonamind.app/.well-known/oauth-protected-resource/mcp/memory`; a token of `/mcp` does not work here and vice versa), a personal token `xm_pat_…` or an API key `xm_live_…` with `memory_bases:read` (read) / `memory_bases:write` (write). The default base is the header `X-Memory-Base` (a slug) or the first shared base. One-line installer for Claude Code: `curl -fsSL https://api.xonamind.app/install/memory.sh | sh -s -- --base ` (registers the server, no secrets involved; OAuth login happens on first use). - Files: `files_upload_from_url` { url, name?, conversationId? } downloads an https file into the workspace (public hosts only, up to 20 MB; png, jpg, jpeg, webp, gif, pdf, txt, md, csv, json) and returns `{ id, name, mime, sizeBytes, kind, url }`: use the id in `attachments` of `conversations_chat_send`. To add a document to a knowledge base pass the URL to `knowledge_documents_add`. - Limits: the token shares the per-minute cap of API keys (`X-RateLimit-*`, 429 `RATE_LIMITED`); every write is in the audit log with the token prefix as the actor. - The same token also works as `Authorization: Bearer xm_pat_…` on `https://api.xonamind.app/v1/admin/*` (GET only for a `read` token; `X-Workspace-Id`, if sent, has to be the token workspace: 403 `WORKSPACE_FORBIDDEN`). ## Optional - [llms.txt](https://api.xonamind.app/llms.txt): this document.