Appearance
SSH machines
Servers the agent may run shell commands on, by name, for the user who saved them — from Chat AI, workflow nodes, the Dedicated Agent and the Teams bot. Saved from the SSH machines tab of the Connections page.
Credentials never come back. password, private_key and passphrase are write-only; the has_* flags say what is stored. They are encrypted at rest and released only to the terminal service, one command at a time, under a short-lived grant Django mints for the turn (30 minutes for a chat turn, an hour for a workflow run). Clients cannot supply a grant themselves: an ssh_grant in a chat or tool payload is stripped.
The host key is pinned on first use. The first successful test records the server's host key. If the server later presents a different key, the machine turns host_key_changed and the agent refuses to use it until you test again with trust_new_host_key: true — do that only when you know the server was rebuilt or its keys rotated.
Commands run without asking. Whatever the agent decides to run runs straight away with the saved login, so save an account with only the access the agent needs. Every command is logged per machine (activity).
Machines are private to their owner — there is no sharing — and each user can save up to 20. A workflow run uses the machines of the workflow's owner, whoever started the run.
Endpoints
| Method | Path | Purpose |
|---|---|---|
| GET | /ssh-machines/ | My machines |
| POST | /ssh-machines/ | Save a machine |
| GET | /ssh-machines/{machine_id}/ | One machine |
| PUT | /ssh-machines/{machine_id}/ | Update a machine |
| DELETE | /ssh-machines/{machine_id}/ | Delete a machine |
| POST | /ssh-machines/{machine_id}/test/ | Test the connection |
| GET | /ssh-machines/{machine_id}/activity/ | Recent commands |
GET /ssh-machines/
My machines
Auth: Session token · In the app: Connections → SSH machines
Response 200 — Your machines, by name.
Array of:
| Field | Type | Description |
|---|---|---|
id | integer | |
name | string | What the agent calls it. 1–64 letters, digits, ., - or _, starting with a letter or digit; unique per user (case-insensitive). |
description | string | Shown to the agent to help it pick the right machine, e.g. Production API server, Ubuntu 22.04. |
host | string | Hostname or IP. Addresses the platform refuses to reach (internal networks, metadata endpoints) are rejected. |
port | integer | Default: 22. |
username | string | The SSH login, e.g. ubuntu. |
auth_type | string ("password", "key") | Default: "password". |
password | string | For password login. Blank on update keeps the stored one. |
private_key | string | For key login: the whole PEM / OpenSSH private key, including the BEGIN and END lines (max 16 KB). PuTTY .ppk keys are not accepted. Blank on update keeps the stored one. |
passphrase | string | The key's passphrase, if it has one. |
has_password | boolean | |
has_private_key | boolean | |
has_passphrase | boolean | |
host_key_fingerprint | string | Fingerprint of the pinned host key; empty until the first successful test. |
status | string ("untested", "ok", "error", "host_key_changed") | untested after saving or changing the login or address. A machine is usable once a test has succeeded (its host key is pinned) and stays usable until its host key changes; a later test that merely fails to connect does not lock it out. |
last_ok_at | string (date-time), nullable | |
last_error | string | |
last_used_at | string (date-time), nullable | When the agent last ran a command on it. |
created_at | string (date-time) | |
updated_at | string (date-time) | |
user | integer |
Example
bash
curl -X GET "https://api.example.com/ssh-machines/" \
-H "Authorization: Token $FINBLADE_TOKEN"POST /ssh-machines/
Save a machine
Saves the machine as untested. It is not usable by the agent until a test succeeds, which also pins its host key. The app saves and tests in one step (Save and test).
Auth: Session token · In the app: Connections → SSH machines → Add a machine
Request body (application/json)
| Field | Type | Required | Description |
|---|---|---|---|
id | integer | no | |
name | string | no | What the agent calls it. 1–64 letters, digits, ., - or _, starting with a letter or digit; unique per user (case-insensitive). |
description | string | no | Shown to the agent to help it pick the right machine, e.g. Production API server, Ubuntu 22.04. |
host | string | no | Hostname or IP. Addresses the platform refuses to reach (internal networks, metadata endpoints) are rejected. |
port | integer | no | Default: 22. |
username | string | no | The SSH login, e.g. ubuntu. |
auth_type | string ("password", "key") | no | Default: "password". |
password | string | no | For password login. Blank on update keeps the stored one. |
private_key | string | no | For key login: the whole PEM / OpenSSH private key, including the BEGIN and END lines (max 16 KB). PuTTY .ppk keys are not accepted. Blank on update keeps the stored one. |
passphrase | string | no | The key's passphrase, if it has one. |
has_password | boolean | no | |
has_private_key | boolean | no | |
has_passphrase | boolean | no | |
host_key_fingerprint | string | no | Fingerprint of the pinned host key; empty until the first successful test. |
status | string ("untested", "ok", "error", "host_key_changed") | no | untested after saving or changing the login or address. A machine is usable once a test has succeeded (its host key is pinned) and stays usable until its host key changes; a later test that merely fails to connect does not lock it out. |
last_ok_at | string (date-time), nullable | no | |
last_error | string | no | |
last_used_at | string (date-time), nullable | no | When the agent last ran a command on it. |
created_at | string (date-time) | no | |
updated_at | string (date-time) | no | |
user | integer | no |
json
{
"name": "web-1",
"description": "Production API server, Ubuntu 22.04",
"host": "10.0.0.5",
"port": 22,
"username": "ubuntu",
"auth_type": "password",
"password": "correct-horse-battery"
}Response 201 — Saved.
| Field | Type | Description |
|---|---|---|
id | integer | |
name | string | What the agent calls it. 1–64 letters, digits, ., - or _, starting with a letter or digit; unique per user (case-insensitive). |
description | string | Shown to the agent to help it pick the right machine, e.g. Production API server, Ubuntu 22.04. |
host | string | Hostname or IP. Addresses the platform refuses to reach (internal networks, metadata endpoints) are rejected. |
port | integer | Default: 22. |
username | string | The SSH login, e.g. ubuntu. |
auth_type | string ("password", "key") | Default: "password". |
password | string | For password login. Blank on update keeps the stored one. |
private_key | string | For key login: the whole PEM / OpenSSH private key, including the BEGIN and END lines (max 16 KB). PuTTY .ppk keys are not accepted. Blank on update keeps the stored one. |
passphrase | string | The key's passphrase, if it has one. |
has_password | boolean | |
has_private_key | boolean | |
has_passphrase | boolean | |
host_key_fingerprint | string | Fingerprint of the pinned host key; empty until the first successful test. |
status | string ("untested", "ok", "error", "host_key_changed") | untested after saving or changing the login or address. A machine is usable once a test has succeeded (its host key is pinned) and stays usable until its host key changes; a later test that merely fails to connect does not lock it out. |
last_ok_at | string (date-time), nullable | |
last_error | string | |
last_used_at | string (date-time), nullable | When the agent last ran a command on it. |
created_at | string (date-time) | |
updated_at | string (date-time) | |
user | integer |
json
{
"id": 7,
"name": "web-1",
"description": "Production API server, Ubuntu 22.04",
"host": "10.0.0.5",
"port": 22,
"username": "ubuntu",
"auth_type": "key",
"has_password": false,
"has_private_key": true,
"has_passphrase": false,
"host_key_fingerprint": "SHA256:4a6f1c0d9e8b7a6c5d4e3f2a1b0c9d8e7f6a5b4c3d2",
"status": "ok",
"last_ok_at": "2026-09-24T10:12:40Z",
"last_error": "",
"last_used_at": "2026-09-25T07:03:11Z",
"created_at": "2026-09-23T14:02:09Z",
"updated_at": "2026-09-24T10:12:40Z",
"user": 42
}Response 400 — Validation error, or you already have 20 machines.
json
{
"name": [
"You already have a machine called \"web-1\"."
]
}Errors
| Status | Body | When |
|---|---|---|
400 | {"detail":"You can save at most 20 machines."} | The per-user limit is reached. |
400 | {"password":["A password is required."]} | auth_type is password and no password is stored or sent. |
400 | {"private_key":["Paste the PRIVATE key (it starts with \"-----BEGIN ... PRIVATE KEY-----\")."]} | A public key, certificate or other text was sent as the key. |
400 | {"username":["Enter the SSH login name (e.g. \"ubuntu\")."]} | The login contains characters an SSH user name cannot. |
Example
bash
curl -X POST "https://api.example.com/ssh-machines/" \
-H "Authorization: Token $FINBLADE_TOKEN" \
-H "Content-Type: application/json" \
-d '{"name":"web-1","description":"Production API server, Ubuntu 22.04","host":"10.0.0.5","port":22,"username":"ubuntu","auth_type":"password","password":"correct-horse-battery"}'GET /ssh-machines/{machine_id}/
One machine
Auth: Session token
Response 200 — The machine.
| Field | Type | Description |
|---|---|---|
id | integer | |
name | string | What the agent calls it. 1–64 letters, digits, ., - or _, starting with a letter or digit; unique per user (case-insensitive). |
description | string | Shown to the agent to help it pick the right machine, e.g. Production API server, Ubuntu 22.04. |
host | string | Hostname or IP. Addresses the platform refuses to reach (internal networks, metadata endpoints) are rejected. |
port | integer | Default: 22. |
username | string | The SSH login, e.g. ubuntu. |
auth_type | string ("password", "key") | Default: "password". |
password | string | For password login. Blank on update keeps the stored one. |
private_key | string | For key login: the whole PEM / OpenSSH private key, including the BEGIN and END lines (max 16 KB). PuTTY .ppk keys are not accepted. Blank on update keeps the stored one. |
passphrase | string | The key's passphrase, if it has one. |
has_password | boolean | |
has_private_key | boolean | |
has_passphrase | boolean | |
host_key_fingerprint | string | Fingerprint of the pinned host key; empty until the first successful test. |
status | string ("untested", "ok", "error", "host_key_changed") | untested after saving or changing the login or address. A machine is usable once a test has succeeded (its host key is pinned) and stays usable until its host key changes; a later test that merely fails to connect does not lock it out. |
last_ok_at | string (date-time), nullable | |
last_error | string | |
last_used_at | string (date-time), nullable | When the agent last ran a command on it. |
created_at | string (date-time) | |
updated_at | string (date-time) | |
user | integer |
json
{
"id": 7,
"name": "web-1",
"description": "Production API server, Ubuntu 22.04",
"host": "10.0.0.5",
"port": 22,
"username": "ubuntu",
"auth_type": "key",
"has_password": false,
"has_private_key": true,
"has_passphrase": false,
"host_key_fingerprint": "SHA256:4a6f1c0d9e8b7a6c5d4e3f2a1b0c9d8e7f6a5b4c3d2",
"status": "ok",
"last_ok_at": "2026-09-24T10:12:40Z",
"last_error": "",
"last_used_at": "2026-09-25T07:03:11Z",
"created_at": "2026-09-23T14:02:09Z",
"updated_at": "2026-09-24T10:12:40Z",
"user": 42
}Response 404 — Not one of your machines.
Example
bash
curl -X GET "https://api.example.com/ssh-machines/{machine_id}/" \
-H "Authorization: Token $FINBLADE_TOKEN"PUT /ssh-machines/{machine_id}/
Update a machine
Partial update — send only what changes. A credential left blank or omitted keeps the stored one; switching auth_type discards the other kind of credential.
Changing the login (username, auth_type or a credential) or the address (host, port) sets the machine back to untested. Changing the address also forgets the pinned host key: a different host is a different server.
Auth: Session token · In the app: Connections → SSH machines → Edit
Request body (application/json)
| Field | Type | Required | Description |
|---|---|---|---|
id | integer | no | |
name | string | no | What the agent calls it. 1–64 letters, digits, ., - or _, starting with a letter or digit; unique per user (case-insensitive). |
description | string | no | Shown to the agent to help it pick the right machine, e.g. Production API server, Ubuntu 22.04. |
host | string | no | Hostname or IP. Addresses the platform refuses to reach (internal networks, metadata endpoints) are rejected. |
port | integer | no | Default: 22. |
username | string | no | The SSH login, e.g. ubuntu. |
auth_type | string ("password", "key") | no | Default: "password". |
password | string | no | For password login. Blank on update keeps the stored one. |
private_key | string | no | For key login: the whole PEM / OpenSSH private key, including the BEGIN and END lines (max 16 KB). PuTTY .ppk keys are not accepted. Blank on update keeps the stored one. |
passphrase | string | no | The key's passphrase, if it has one. |
has_password | boolean | no | |
has_private_key | boolean | no | |
has_passphrase | boolean | no | |
host_key_fingerprint | string | no | Fingerprint of the pinned host key; empty until the first successful test. |
status | string ("untested", "ok", "error", "host_key_changed") | no | untested after saving or changing the login or address. A machine is usable once a test has succeeded (its host key is pinned) and stays usable until its host key changes; a later test that merely fails to connect does not lock it out. |
last_ok_at | string (date-time), nullable | no | |
last_error | string | no | |
last_used_at | string (date-time), nullable | no | When the agent last ran a command on it. |
created_at | string (date-time) | no | |
updated_at | string (date-time) | no | |
user | integer | no |
json
{
"description": "Production API server (Ubuntu 24.04)"
}Response 200 — Updated.
| Field | Type | Description |
|---|---|---|
id | integer | |
name | string | What the agent calls it. 1–64 letters, digits, ., - or _, starting with a letter or digit; unique per user (case-insensitive). |
description | string | Shown to the agent to help it pick the right machine, e.g. Production API server, Ubuntu 22.04. |
host | string | Hostname or IP. Addresses the platform refuses to reach (internal networks, metadata endpoints) are rejected. |
port | integer | Default: 22. |
username | string | The SSH login, e.g. ubuntu. |
auth_type | string ("password", "key") | Default: "password". |
password | string | For password login. Blank on update keeps the stored one. |
private_key | string | For key login: the whole PEM / OpenSSH private key, including the BEGIN and END lines (max 16 KB). PuTTY .ppk keys are not accepted. Blank on update keeps the stored one. |
passphrase | string | The key's passphrase, if it has one. |
has_password | boolean | |
has_private_key | boolean | |
has_passphrase | boolean | |
host_key_fingerprint | string | Fingerprint of the pinned host key; empty until the first successful test. |
status | string ("untested", "ok", "error", "host_key_changed") | untested after saving or changing the login or address. A machine is usable once a test has succeeded (its host key is pinned) and stays usable until its host key changes; a later test that merely fails to connect does not lock it out. |
last_ok_at | string (date-time), nullable | |
last_error | string | |
last_used_at | string (date-time), nullable | When the agent last ran a command on it. |
created_at | string (date-time) | |
updated_at | string (date-time) | |
user | integer |
json
{
"id": 7,
"name": "web-1",
"description": "Production API server, Ubuntu 22.04",
"host": "10.0.0.5",
"port": 22,
"username": "ubuntu",
"auth_type": "key",
"has_password": false,
"has_private_key": true,
"has_passphrase": false,
"host_key_fingerprint": "SHA256:4a6f1c0d9e8b7a6c5d4e3f2a1b0c9d8e7f6a5b4c3d2",
"status": "ok",
"last_ok_at": "2026-09-24T10:12:40Z",
"last_error": "",
"last_used_at": "2026-09-25T07:03:11Z",
"created_at": "2026-09-23T14:02:09Z",
"updated_at": "2026-09-24T10:12:40Z",
"user": 42
}Response 400 — Validation error.
json
{
"username": [
"A user with that username already exists."
]
}Response 404 — Not one of your machines.
Example
bash
curl -X PUT "https://api.example.com/ssh-machines/{machine_id}/" \
-H "Authorization: Token $FINBLADE_TOKEN" \
-H "Content-Type: application/json" \
-d '{"description":"Production API server (Ubuntu 24.04)"}'DELETE /ssh-machines/{machine_id}/
Delete a machine
The agent can no longer use it. Its command history is kept.
Auth: Session token · In the app: Connections → SSH machines → Delete
Response 200 — Deleted.
| Field | Type | Description |
|---|---|---|
message | string |
json
{
"message": "Success"
}Response 404 — Not one of your machines.
Example
bash
curl -X DELETE "https://api.example.com/ssh-machines/{machine_id}/" \
-H "Authorization: Token $FINBLADE_TOKEN"POST /ssh-machines/{machine_id}/test/
Test the connection
Connects through the terminal service, runs a harmless command and records the outcome on the machine (status, last_ok_at, last_error). The first success pins the server's host key.
Always answers 200 with ok — an unreachable server is a result, not an API error. error_type: host_key_changed means the server presented a different key from the pinned one; test again with trust_new_host_key: true only if you know why it changed. Allow up to 45 seconds.
Auth: Session token · In the app: Connections → SSH machines → Test / Trust new key
Path parameters
| Field | Type | Required | Description |
|---|---|---|---|
machine_id | integer | yes |
Request body (application/json)
| Field | Type | Required | Description |
|---|---|---|---|
trust_new_host_key | boolean | no | Replace the pinned host key with the one the server presents now. Default: false. |
json
{
"trust_new_host_key": false
}Response 200 — The outcome, and the machine as updated.
| Field | Type | Description |
|---|---|---|
ok | boolean | |
error | string, nullable | |
error_type | string, nullable | The terminal service's reason code, when it gives one. host_key_changed is the one that needs a decision from you. |
fingerprint | string, nullable | The host key the server presented. |
output | string | What the test command printed (first 500 characters). |
latency_ms | integer, nullable | |
machine | object | A saved server. Credentials are write-only; the has_* flags report what is stored. |
machine.id | integer | |
machine.name | string | What the agent calls it. 1–64 letters, digits, ., - or _, starting with a letter or digit; unique per user (case-insensitive). |
machine.description | string | Shown to the agent to help it pick the right machine, e.g. Production API server, Ubuntu 22.04. |
machine.host | string | Hostname or IP. Addresses the platform refuses to reach (internal networks, metadata endpoints) are rejected. |
machine.port | integer | Default: 22. |
machine.username | string | The SSH login, e.g. ubuntu. |
machine.auth_type | string ("password", "key") | Default: "password". |
machine.password | string | For password login. Blank on update keeps the stored one. |
machine.private_key | string | For key login: the whole PEM / OpenSSH private key, including the BEGIN and END lines (max 16 KB). PuTTY .ppk keys are not accepted. Blank on update keeps the stored one. |
machine.passphrase | string | The key's passphrase, if it has one. |
machine.has_password | boolean | |
machine.has_private_key | boolean | |
machine.has_passphrase | boolean | |
machine.host_key_fingerprint | string | Fingerprint of the pinned host key; empty until the first successful test. |
machine.status | string ("untested", "ok", "error", "host_key_changed") | untested after saving or changing the login or address. A machine is usable once a test has succeeded (its host key is pinned) and stays usable until its host key changes; a later test that merely fails to connect does not lock it out. |
machine.last_ok_at | string (date-time), nullable | |
machine.last_error | string | |
machine.last_used_at | string (date-time), nullable | When the agent last ran a command on it. |
machine.created_at | string (date-time) | |
machine.updated_at | string (date-time) | |
machine.user | integer |
json
{
"ok": true,
"error": null,
"error_type": null,
"fingerprint": "SHA256:4a6f1c0d9e8b7a6c5d4e3f2a1b0c9d8e7f6a5b4c3d2",
"output": "Linux web-1 6.8.0-45-generic x86_64",
"latency_ms": 612,
"machine": {
"id": 7,
"name": "web-1",
"status": "ok"
}
}Response 404 — Not one of your machines.
Example
bash
curl -X POST "https://api.example.com/ssh-machines/<machine_id>/test/" \
-H "Authorization: Token $FINBLADE_TOKEN" \
-H "Content-Type: application/json" \
-d '{"trust_new_host_key":false}'GET /ssh-machines/{machine_id}/activity/
Recent commands
The last 50 commands run on this machine, newest first.
Auth: Session token · In the app: Connections → SSH machines → Recent commands
Path parameters
| Field | Type | Required | Description |
|---|---|---|---|
machine_id | integer | yes |
Response 200 — Commands.
Array of:
| Field | Type | Description |
|---|---|---|
id | integer | |
machine_name | string | |
command | string | The command as run (truncated to 4,000 characters). |
source | string ("chat", "teams", "workflow", "dedicated", "test") | Where the command came from. |
conversation_id | string, nullable | The chat conversation, for chat, teams and dedicated. |
workflow_id | string, nullable | The workflow, for workflow. |
exit_code | integer, nullable | null while the command is still running. |
duration_ms | integer, nullable | |
error | string | |
created_at | string (date-time) | |
finished_at | string (date-time), nullable |
Response 404 — Not one of your machines.
Example
bash
curl -X GET "https://api.example.com/ssh-machines/<machine_id>/activity/" \
-H "Authorization: Token $FINBLADE_TOKEN"