Base URL https://app.mandala.computer/api/v1. Every request carries an API key, and failures take the shape under Errors.
GET/ssh-keys
List your SSH keysviewer+
The SSH public keys registered to you — not to the account. A key identifies one person, so this is the same list whichever account your key or session is acting on. A key added from the dashboard reaches the computers of every account where you are an owner or member. A key added through an API key or a connected app reaches only the account that credential acts on, and is removed when the credential is revoked. Each key’s reach says which of those it is, from the account this request acts on, without naming any other. Viewers cannot connect.
HOW TO CONNECT. Switch SSH on for a computer (PUT /computers/{id}/ssh), then ssh -J mandala@ssh.mandala.computer:2222 user@<computer id or name>. The jump host checks the key you offer against this list and your role on the computer’s account; the computer’s own sshd then checks it again. A name that matches computers on more than one of your accounts is refused — use the id.
Connecting to a suspended computer resumes it — a start like any other, charged and refusable by your plan. A stopped computer refuses the connection; start it first.
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%/}"'/ssh-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.ssh_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.sshKeys.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
Your keys, oldest first.
Response fields
idalways
string
sshk- and sixteen hex characters.
namealways
string
Your label for it. Defaults to the comment that followed the key when you added it.
public_keyalways
string
The key as <type> <base64>, re-encoded from what you sent: no options and no comment. This is the line the platform writes into the computer.
fingerprintalways
string
SHA256: and 43 characters, exactly as ssh-keygen -l -f <file> prints it.
key_typealways
string
One of ssh-ed25519, ecdsa-sha2-nistp256, ecdsa-sha2-nistp384, ecdsa-sha2-nistp521, sk-ssh-ed25519@openssh.com, sk-ecdsa-sha2-nistp256@openssh.com or ssh-rsa.
created_atalways
string
RFC 3339 timestamp.
last_used_atalways
string or null
When this key last opened a connection to a computer. null until it has.
reachalways
"everywhere" | "this_account" | "another_account"
Where this key can connect, from the account this request acts on. everywhere: it was added from the dashboard and works on every account where you are an owner or member. this_account: it was added through an API key or connected app on this account and works here only. another_account: it was added through an API key or connected app on a different account and does not work here; which account is not said. A key is registered once, so adding it again is a 409: to use one key on every account, remove it and add it from the dashboard; otherwise use a separate key here.
POST/ssh-keys
Add an SSH keymember+
Registers a public key to you. Accepted: Ed25519, ECDSA (P-256, P-384, P-521), the FIDO security-key forms sk-ssh-ed25519@openssh.com and sk-ecdsa-sha2-nistp256@openssh.com, and RSA of at least 3072 bits. Refused with a 400: DSA, shorter RSA, certificates, a line with authorized_keys options in front of the key, and anything that is not one key on one line.
A key can belong to one person only; registering one that is already registered is a 409. Each person may hold eight; the ninth is a 409.
The key is bound to the account this API key (or connected app) acts on, and to the credential itself: it is written into every computer with SSH on, on that account only, within moments, and it is removed when the credential is revoked. A key added from the dashboard instead reaches every account where you are an owner or member. The answer does not wait for the computers.
An API key confined to a workspace cannot add or remove SSH keys (403): an SSH key reaches further than that workspace.
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%/}"'/ssh-keys' \
-H "Authorization: Bearer $MANDALA_API_KEY" \
-H "Content-Type: application/json" \
--data-raw '{"public_key":"ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIMxlN5MDRT9cXdHi871o7Ty3dKfNLt8mNmjSWtwv6DTw you@laptop","name":"laptop"}'
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("{\"public_key\":\"ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIMxlN5MDRT9cXdHi871o7Ty3dKfNLt8mNmjSWtwv6DTw you@laptop\",\"name\":\"laptop\"}")
result = client.ssh_keys.add(body["public_key"], name=body["name"])
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 body = {"public_key":"ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIMxlN5MDRT9cXdHi871o7Ty3dKfNLt8mNmjSWtwv6DTw you@laptop","name":"laptop"};
const result = await client.sshKeys.add({publicKey: body.public_key, name: body.name});
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"
}
Request body
public_keyrequired
string
One line from a .pub file: <type> <base64> [comment], at most 8192 characters. The comment is dropped. The file read whole is fine: one trailing line ending (\n or \r\n) is ignored and not counted, but a second line is a 400.
name
string
A label, up to 60 characters after trimming. Left out or null, it is the comment on the line, with any control characters removed and cut to 60 characters, or key if there is none; an empty or blank name is key too.
Response
The key as registered.
Response fields
idalways
string
sshk- and sixteen hex characters.
namealways
string
Your label for it. Defaults to the comment that followed the key when you added it.
public_keyalways
string
The key as <type> <base64>, re-encoded from what you sent: no options and no comment. This is the line the platform writes into the computer.
fingerprintalways
string
SHA256: and 43 characters, exactly as ssh-keygen -l -f <file> prints it.
key_typealways
string
One of ssh-ed25519, ecdsa-sha2-nistp256, ecdsa-sha2-nistp384, ecdsa-sha2-nistp521, sk-ssh-ed25519@openssh.com, sk-ecdsa-sha2-nistp256@openssh.com or ssh-rsa.
created_atalways
string
RFC 3339 timestamp.
last_used_atalways
string or null
When this key last opened a connection to a computer. null until it has.
reachalways
"everywhere" | "this_account" | "another_account"
Where this key can connect, from the account this request acts on. everywhere: it was added from the dashboard and works on every account where you are an owner or member. this_account: it was added through an API key or connected app on this account and works here only. another_account: it was added through an API key or connected app on a different account and does not work here; which account is not said. A key is registered once, so adding it again is a 409: to use one key on every account, remove it and add it from the dashboard; otherwise use a separate key here.
Failures
403
Also, with no reason and checked first: a holder who has not verified their email address (Verify your email before adding an SSH key.); verify it in the dashboard.
409
Either refusal here carries no reason, and neither clears by waiting: the key is already registered (to you or to someone else), or you already hold eight keys. Remove a key first, or register a different one; sending the same request again is refused the same way.
413
This route reads at most 16 KiB (16384 bytes) of request body, less than the general cap.
DELETE/ssh-keys/{id}
Remove an SSH keymember+
Removes one of your keys. New connections with it are refused at once. Within moments it is removed from the computers it was written into, and any SSH session opened with it is closed as each computer receives the new key list. Sessions opened with your other keys are not affected. Not available to a workspace-scoped API key (403).
An API key or connected app removes only a key bound to the account it acts on, one whose reach is this_account. Any other key, whether its reach is everywhere (added from the dashboard, or through the API before keys were bound to an account) or another_account, is a 404, as for a key that does not exist. Remove it from the dashboard; a key whose reach is another_account can also be removed with an API key on the account it was added through.
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%/}"'/ssh-keys/sshk-a1b2c3d4e5f60718' \
-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 = "sshk-a1b2c3d4e5f60718"
result = client.ssh_keys.remove(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 = "sshk-a1b2c3d4e5f60718";
const result = await client.sshKeys.remove(id);
console.log(JSON.stringify(result, null, 2));
Always true. A failure is a status code, not ok: false.
GET/computers/{id}/ssh
Read a computer’s SSH settingviewer+
Whether SSH is on for this computer, whether it can work here at all, and whether the computer’s hypervisor has the current setting yet. Any role may read it.
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. SDK setup first GETs the computer. Python start/stop/suspend/restart also refresh it with another GET; TypeScript refreshes only if the action returns an acknowledgement.
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%/}"'/computers/vm-a1b2c3d4e5f6/ssh' \
-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 = "vm-a1b2c3d4e5f6"
c = client.computers.get(id)
result = c.ssh_access()
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 = "vm-a1b2c3d4e5f6";
const c = await client.computers.get(id);
const result = await c.sshAccess();
console.log(JSON.stringify(result, null, 2));
Whether SSH is switched on for this computer. Off until somebody switches it on.
availablealways
boolean or null
Whether this computer can run SSH at all. false for a computer made from a template image that predates SSH — create a new computer from the current template to use it. null when the computer has not been asked yet; it is asked when it next starts.
pendingalways
boolean
Whether the computer’s hypervisor has yet to receive the current setting and key list. It is sent again automatically; a connection attempt sends it first.
key_countalways
integer
How many keys may log in: every key of every owner and member of the account, except keys added through an API key or connected app on a different account, and except the keys of a person whose invitation is not yet accepted, who is suspended, or whose seat is suspended. None at all while the account itself is suspended. 0 while SSH is off.
keys_pushedalways
integer
How many of those the computer is given. The same as key_count unless the account holds more keys than one computer accepts (200); then the keys of the members who joined most recently, and each person’s newest keys, are the ones left out, and connecting with one of them is refused.
erroralways
string or null
Set when the computer’s hypervisor refused the current setting. It is not sent again until a key or this setting changes; pending is false meanwhile. null otherwise.
PUT/computers/{id}/ssh
Switch SSH on or offmember+
Turns SSH on or off for this computer. On, the computer runs an SSH server reachable only through the platform’s jump host, and accepts the keys of every owner and member of the account — not those of a person whose invitation is unaccepted, who is suspended or whose seat is suspended, and none while the account is suspended (see key_count); off, the server stops, the key file is removed and open SSH sessions are closed. No restart either way.
A computer made from a template image that predates SSH cannot run it: the setting is stored and the answer says available: false. Create a new computer from the current template instead.
If the computer’s hypervisor cannot be reached the setting is still stored, the answer says pending: true, and it is delivered when the hypervisor is back — at the latest when somebody next connects or reads this setting.
Connecting to a suspended computer resumes it — a start like any other, charged and refusable by your plan. A stopped computer refuses the connection; start it first.
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. SDK setup first GETs the computer. Python start/stop/suspend/restart also refresh it with another GET; TypeScript refreshes only if the action returns an acknowledgement.
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 PUT "${base_url%/}"'/computers/vm-a1b2c3d4e5f6/ssh' \
-H "Authorization: Bearer $MANDALA_API_KEY" \
-H "Content-Type: application/json" \
--data-raw '{"enabled":true}'
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:
id = "vm-a1b2c3d4e5f6"
body = json.loads("{\"enabled\":true}")
c = client.computers.get(id)
result = c.set_ssh_access(body["enabled"])
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 = "vm-a1b2c3d4e5f6";
const body = {"enabled":true};
const c = await client.computers.get(id);
const result = await c.setSshAccess(body.enabled);
console.log(JSON.stringify(result, null, 2));
Whether SSH is switched on for this computer. Off until somebody switches it on.
availablealways
boolean or null
Whether this computer can run SSH at all. false for a computer made from a template image that predates SSH — create a new computer from the current template to use it. null when the computer has not been asked yet; it is asked when it next starts.
pendingalways
boolean
Whether the computer’s hypervisor has yet to receive the current setting and key list. It is sent again automatically; a connection attempt sends it first.
key_countalways
integer
How many keys may log in: every key of every owner and member of the account, except keys added through an API key or connected app on a different account, and except the keys of a person whose invitation is not yet accepted, who is suspended, or whose seat is suspended. None at all while the account itself is suspended. 0 while SSH is off.
keys_pushedalways
integer
How many of those the computer is given. The same as key_count unless the account holds more keys than one computer accepts (200); then the keys of the members who joined most recently, and each person’s newest keys, are the ones left out, and connecting with one of them is refused.
erroralways
string or null
Set when the computer’s hypervisor refused the current setting. It is not sent again until a key or this setting changes; pending is false meanwhile. null otherwise.
Failures
413
This route reads at most 16 KiB (16384 bytes) of request body, less than the general cap.