Who a key is, and minting and revoking keys from a key the dashboard allowed to.
Base URL https://app.mandala.computer/api/v1. Every request carries an API key, and failures take the shape under Errors.
GET/whoami
Who am Iviewer+
The credential this request was made with: the person holding it, the account and role it acts with, the workspace it is confined to (null for an account-wide key), and the key itself.
The role is the holder’s role now, not when the key was minted. A suspended account may still call this, and account.status says so.
A workspace-scoped key is not told its holder’s email address or name, nor the account’s name, because such a key may be in an end customer’s hands: each is null. Its account.plan is null too; GET /account reports the plan and its ceilings.
Set MANDALA_API_KEY to your bearer key. MANDALA_BASE_URL, when set, is the full API base including /api/v1. Replace the illustrative resource IDs with yours. These pinned 0.16.0 SDK and direct HTTP examples do not read saved device-login credentials.
curl
base_url="${MANDALA_BASE_URL:-}"
if [ -z "$base_url" ]; then base_url='https://app.mandala.computer/api/v1'; fi
curl --fail-with-body "${base_url%/}"'/whoami' \
-H "Authorization: Bearer $MANDALA_API_KEY"
Python — Mandala SDK 0.16.0
# Python 3.10+: pip install mandala-computer==0.16.0
import os
from mandala_computer import Client
with Client(api_key=os.environ["MANDALA_API_KEY"], base_url=os.environ.get("MANDALA_BASE_URL") or "https://app.mandala.computer/api/v1") as client:
result = client.account.whoami()
print(result)
TypeScript — Mandala SDK 0.16.0
// Node.js 22+: npm install mandala-computer@0.16.0
import { Client } from "mandala-computer";
const apiKey = process.env.MANDALA_API_KEY;
if (!apiKey) throw new Error("Set MANDALA_API_KEY");
const client = new Client({apiKey, baseUrl: process.env.MANDALA_BASE_URL || "https://app.mandala.computer/api/v1"});
const result = await client.account.whoami();
console.log(JSON.stringify(result, null, 2));
A suspended account may still make this read: a 403 here is about the credential or the person holding it, never the account’s suspension.
GET/api-keys
List API keysviewer+
Your API keys on this account, newest first. Needs a key with manage_keys on: that is set for a key from the dashboard (Settings → Credentials → API keys → the key’s menu → Allow managing keys), is off for every key until then, and every other key is refused this route with 403.
Only the holder’s own keys, and only those acting on this account. A key confined to a workspace lists the keys confined to that same workspace and no others.
Set MANDALA_API_KEY to your bearer key. MANDALA_BASE_URL, when set, is the full API base including /api/v1. Replace the illustrative resource IDs with yours. These pinned 0.16.0 SDK and direct HTTP examples do not read saved device-login credentials.
curl
base_url="${MANDALA_BASE_URL:-}"
if [ -z "$base_url" ]; then base_url='https://app.mandala.computer/api/v1'; fi
curl --fail-with-body "${base_url%/}"'/api-keys' \
-H "Authorization: Bearer $MANDALA_API_KEY"
Python — Mandala SDK 0.16.0
# Python 3.10+: pip install mandala-computer==0.16.0
import os
from mandala_computer import Client
with Client(api_key=os.environ["MANDALA_API_KEY"], base_url=os.environ.get("MANDALA_BASE_URL") or "https://app.mandala.computer/api/v1") as client:
result = client.api_keys.list()
print(result)
TypeScript — Mandala SDK 0.16.0
// Node.js 22+: npm install mandala-computer@0.16.0
import { Client } from "mandala-computer";
const apiKey = process.env.MANDALA_API_KEY;
if (!apiKey) throw new Error("Set MANDALA_API_KEY");
const client = new Client({apiKey, baseUrl: process.env.MANDALA_BASE_URL || "https://app.mandala.computer/api/v1"});
const result = await client.apiKeys.list();
console.log(JSON.stringify(result, null, 2));
405 Unsupported method on this public path; HEAD returns these headers without a body.
HTTP 405
Content-Type: application/json
X-Request-ID: 3f2c9a1e-7b4d-4e8a-9c61-0d5f2b8e4a17
Allow: GET, HEAD, POST, OPTIONS
{
"error": "method not allowed",
"request_id": "3f2c9a1e-7b4d-4e8a-9c61-0d5f2b8e4a17"
}
Response
The keys, newest first. The raw keys are never listed.
Response fields
idalways
string
key- and twelve hex characters. What an audit entry names as apikey:<id>.
namealways
string or null
Its label, up to 60 characters, or null when it was minted without one. For a hosted-MCP (OAuth) credential, which only GET /whoami answers, Connected app: followed by the connected app’s name, up to 100 characters in all.
prefixalways
string
The first twelve characters of the key and an ellipsis, for recognising it. Not a secret. oauth for a hosted-MCP (OAuth) credential, as GET /whoami answers it.
created_atalways
string
RFC 3339 timestamp.
last_used_atalways
string or null
RFC 3339 timestamp of its last authenticated request, or null until it has one.
workspace_idalways
string or null
The workspace it is confined to, or null for the whole account.
workspace_namealways
string or null
That workspace’s name, or null for an account-wide key.
manage_keysalways
boolean
Whether this key may use /api-keys. With it a key lists its holder’s keys; creating and revoking also need the holder to be a member or owner, so a viewer’s key can only list. Set only from the dashboard; no key can set it.
minted_by_key_idalways
string or null
The id of the API key that minted this one over the API, or null for a key minted from the dashboard. Kept after that key is revoked.
Failures
403
Also the answer, with no reason, to a key that does not have manage_keys on.
POST/api-keys
Create an API keymember+
Mints a key for the key’s holder on this account. The answer carries the raw key in raw, once: it is stored only as a hash and no route returns it again. Needs a key with manage_keys on, and a member or owner role (a viewer mints from the dashboard).
A key cannot mint a key with manage_keys on (403): only the dashboard grants it. Nor can it mint one wider than itself. Without workspace_id a key confined to a workspace mints into that workspace, and naming any other scope, null included, is a 403. An account-wide key may name any workspace of the account (an unknown one is a 400) or none, for another account-wide key.
The body is optional. No body, or one of nothing but whitespace, mints an unnamed key exactly as {} does.
The account holds at most 1000 keys, whoever minted them; the next is a 400.
Set MANDALA_API_KEY to your bearer key. MANDALA_BASE_URL, when set, is the full API base including /api/v1. Replace the illustrative resource IDs with yours. These pinned 0.16.0 SDK and direct HTTP examples do not read saved device-login credentials.
curl
base_url="${MANDALA_BASE_URL:-}"
if [ -z "$base_url" ]; then base_url='https://app.mandala.computer/api/v1'; fi
curl --fail-with-body -X POST "${base_url%/}"'/api-keys' \
-H "Authorization: Bearer $MANDALA_API_KEY" \
-H "Content-Type: application/json" \
--data-raw '{"name":"ci"}'
Python — Mandala SDK 0.16.0
# Python 3.10+: pip install mandala-computer==0.16.0
import json, os
from mandala_computer import Client
with Client(api_key=os.environ["MANDALA_API_KEY"], base_url=os.environ.get("MANDALA_BASE_URL") or "https://app.mandala.computer/api/v1") as client:
body = json.loads("{\"name\":\"ci\"}")
result = client.api_keys.create(name=body["name"])
print("Key returned once; store result.key securely without logging it.")
TypeScript — Mandala SDK 0.16.0
// Node.js 22+: npm install mandala-computer@0.16.0
import { Client } from "mandala-computer";
const apiKey = process.env.MANDALA_API_KEY;
if (!apiKey) throw new Error("Set MANDALA_API_KEY");
const client = new Client({apiKey, baseUrl: process.env.MANDALA_BASE_URL || "https://app.mandala.computer/api/v1"});
const body = {"name":"ci"};
const result = await client.apiKeys.create({name: body.name});
console.log("Key returned once; store result.key securely without logging it.");
405 Unsupported method on this public path; HEAD returns these headers without a body.
HTTP 405
Content-Type: application/json
X-Request-ID: 3f2c9a1e-7b4d-4e8a-9c61-0d5f2b8e4a17
Allow: GET, HEAD, POST, OPTIONS
{
"error": "method not allowed",
"request_id": "3f2c9a1e-7b4d-4e8a-9c61-0d5f2b8e4a17"
}
Request body
name
string or null
A label, up to 60 characters; longer is cut; control, bidirectional and invisible formatting characters (such as U+200B and U+2060) are refused (400), while the joiners U+200C and U+200D and the variation selectors (such as U+FE0F) that emoji and some scripts need are allowed. Optional; null is the same as leaving it out.
workspace_id
string or null
The workspace to confine the new key to, or leave it out. See above for a workspace-scoped key.
manage_keys
boolean
Must be false or absent. true is refused: only the dashboard grants it.
Response
The key, with the raw key in raw.
Response fields
idalways
string
As on ApiKey.
namealways
string or null
As on ApiKey.
prefixalways
string
As on ApiKey.
created_atalways
string
RFC 3339 timestamp.
last_used_atalways
string or null
Always null: the key has not been used yet.
workspace_idalways
string or null
As on ApiKey.
workspace_namealways
string or null
As on ApiKey.
manage_keysalways
boolean
As on ApiKey. Always false for a key minted over the API.
minted_by_key_idalways
string or null
As on ApiKey. Always the id of the key that made this request.
rawalways
string
The key: com_ and 48 hex characters. Shown here and never again — store it now. Send it as Authorization: Bearer <raw>.
Failures
403
Also, with no reason: this key does not have manage_keys on; the body asked for manage_keys: true; a workspace-scoped key named another scope; or the key’s holder has not verified their email address (Verify your email before creating an API key.).
413
This route reads at most 4 KiB (4096 bytes) of request body, less than the general cap.
DELETE/api-keys/{id}
Revoke an API keymember+
Revokes one of your keys on this account; anything using it is refused from its next request. Needs a key with manage_keys on, and a member or owner role. A key outside your reach — another person’s, another account’s, or for a workspace-scoped key any key not confined to its workspace — is the same 404 as one that does not exist.
A key may revoke itself; the request that does it is its last.
The SSH keys added through this key go with it: the gateway refuses them to any connection it has not already let through, and each is taken off the computers it was written into as each computer receives the new key list. Until a computer does, a connection already let through can still reach it; the answer does not wait for the computers. No other API key is revoked: the keys it minted keep working, and each names it in minted_by_key_id; the dashboard can revoke them with it, or in one step after it has been revoked.
Set MANDALA_API_KEY to your bearer key. MANDALA_BASE_URL, when set, is the full API base including /api/v1. Replace the illustrative resource IDs with yours. These pinned 0.16.0 SDK and direct HTTP examples do not read saved device-login credentials.
curl
base_url="${MANDALA_BASE_URL:-}"
if [ -z "$base_url" ]; then base_url='https://app.mandala.computer/api/v1'; fi
curl --fail-with-body -X DELETE "${base_url%/}"'/api-keys/key-a1b2c3d4e5f6' \
-H "Authorization: Bearer $MANDALA_API_KEY"
Python — Mandala SDK 0.16.0
# Python 3.10+: pip install mandala-computer==0.16.0
import os
from mandala_computer import Client
with Client(api_key=os.environ["MANDALA_API_KEY"], base_url=os.environ.get("MANDALA_BASE_URL") or "https://app.mandala.computer/api/v1") as client:
id = "key-a1b2c3d4e5f6"
result = client.api_keys.revoke(id)
print(result)
TypeScript — Mandala SDK 0.16.0
// Node.js 22+: npm install mandala-computer@0.16.0
import { Client } from "mandala-computer";
const apiKey = process.env.MANDALA_API_KEY;
if (!apiKey) throw new Error("Set MANDALA_API_KEY");
const client = new Client({apiKey, baseUrl: process.env.MANDALA_BASE_URL || "https://app.mandala.computer/api/v1"});
const id = "key-a1b2c3d4e5f6";
const result = await client.apiKeys.revoke(id);
console.log(JSON.stringify(result, null, 2));