curl --request POST \
--url https://api.reasonmachines.ai/v3/organizations/{orgId}/ssh-connections \
--header 'Authorization: Bearer <token>' \
--header 'Content-Type: application/json' \
--data '
{
"name": "Build host",
"host": "host.example.com",
"username": "ubuntu",
"authentication": {
"type": "private_key",
"private_key": "<load from your secret manager>"
},
"consent_host_access": true,
"idempotency_key": "save-build-host-001"
}
'import requests
url = "https://api.reasonmachines.ai/v3/organizations/{orgId}/ssh-connections"
payload = {
"name": "Build host",
"host": "host.example.com",
"username": "ubuntu",
"authentication": {
"type": "private_key",
"private_key": "<load from your secret manager>"
},
"consent_host_access": True,
"idempotency_key": "save-build-host-001"
}
headers = {
"Authorization": "Bearer <token>",
"Content-Type": "application/json"
}
response = requests.post(url, json=payload, headers=headers)
print(response.text)const options = {
method: 'POST',
headers: {Authorization: 'Bearer <token>', 'Content-Type': 'application/json'},
body: JSON.stringify({
name: 'Build host',
host: 'host.example.com',
username: 'ubuntu',
authentication: {type: 'private_key', private_key: '<load from your secret manager>'},
consent_host_access: true,
idempotency_key: 'save-build-host-001'
})
};
fetch('https://api.reasonmachines.ai/v3/organizations/{orgId}/ssh-connections', options)
.then(res => res.json())
.then(res => console.log(res))
.catch(err => console.error(err));<?php
$curl = curl_init();
curl_setopt_array($curl, [
CURLOPT_URL => "https://api.reasonmachines.ai/v3/organizations/{orgId}/ssh-connections",
CURLOPT_RETURNTRANSFER => true,
CURLOPT_ENCODING => "",
CURLOPT_MAXREDIRS => 10,
CURLOPT_TIMEOUT => 30,
CURLOPT_HTTP_VERSION => CURL_HTTP_VERSION_1_1,
CURLOPT_CUSTOMREQUEST => "POST",
CURLOPT_POSTFIELDS => json_encode([
'name' => 'Build host',
'host' => 'host.example.com',
'username' => 'ubuntu',
'authentication' => [
'type' => 'private_key',
'private_key' => '<load from your secret manager>'
],
'consent_host_access' => true,
'idempotency_key' => 'save-build-host-001'
]),
CURLOPT_HTTPHEADER => [
"Authorization: Bearer <token>",
"Content-Type: application/json"
],
]);
$response = curl_exec($curl);
$err = curl_error($curl);
curl_close($curl);
if ($err) {
echo "cURL Error #:" . $err;
} else {
echo $response;
}package main
import (
"fmt"
"strings"
"net/http"
"io"
)
func main() {
url := "https://api.reasonmachines.ai/v3/organizations/{orgId}/ssh-connections"
payload := strings.NewReader("{\n \"name\": \"Build host\",\n \"host\": \"host.example.com\",\n \"username\": \"ubuntu\",\n \"authentication\": {\n \"type\": \"private_key\",\n \"private_key\": \"<load from your secret manager>\"\n },\n \"consent_host_access\": true,\n \"idempotency_key\": \"save-build-host-001\"\n}")
req, _ := http.NewRequest("POST", url, payload)
req.Header.Add("Authorization", "Bearer <token>")
req.Header.Add("Content-Type", "application/json")
res, _ := http.DefaultClient.Do(req)
defer res.Body.Close()
body, _ := io.ReadAll(res.Body)
fmt.Println(string(body))
}HttpResponse<String> response = Unirest.post("https://api.reasonmachines.ai/v3/organizations/{orgId}/ssh-connections")
.header("Authorization", "Bearer <token>")
.header("Content-Type", "application/json")
.body("{\n \"name\": \"Build host\",\n \"host\": \"host.example.com\",\n \"username\": \"ubuntu\",\n \"authentication\": {\n \"type\": \"private_key\",\n \"private_key\": \"<load from your secret manager>\"\n },\n \"consent_host_access\": true,\n \"idempotency_key\": \"save-build-host-001\"\n}")
.asString();require 'uri'
require 'net/http'
url = URI("https://api.reasonmachines.ai/v3/organizations/{orgId}/ssh-connections")
http = Net::HTTP.new(url.host, url.port)
http.use_ssl = true
request = Net::HTTP::Post.new(url)
request["Authorization"] = 'Bearer <token>'
request["Content-Type"] = 'application/json'
request.body = "{\n \"name\": \"Build host\",\n \"host\": \"host.example.com\",\n \"username\": \"ubuntu\",\n \"authentication\": {\n \"type\": \"private_key\",\n \"private_key\": \"<load from your secret manager>\"\n },\n \"consent_host_access\": true,\n \"idempotency_key\": \"save-build-host-001\"\n}"
response = http.request(request)
puts response.read_body{
"connection": {
"id": "3c90c3cc-0d44-4b50-8888-8dd25736052a",
"name": "<string>",
"host": "<string>",
"port": 123,
"username": "<string>",
"workspace_path": "<string>",
"auth_type": "password",
"host_key_fingerprint": "<string>",
"pending_host_key_fingerprint": "<string>",
"device_id": "<string>",
"status": "saved",
"error_code": "<string>",
"created_at": "<string>",
"updated_at": "<string>"
},
"replayed": true
}{
"connection": {
"id": "3c90c3cc-0d44-4b50-8888-8dd25736052a",
"name": "<string>",
"host": "<string>",
"port": 123,
"username": "<string>",
"workspace_path": "<string>",
"auth_type": "password",
"host_key_fingerprint": "<string>",
"pending_host_key_fingerprint": "<string>",
"device_id": "<string>",
"status": "saved",
"error_code": "<string>",
"created_at": "<string>",
"updated_at": "<string>"
},
"replayed": true
}Add an SSH connection
Save encrypted password or private-key authentication for a Linux host. Does not connect yet. Explicit host-access consent and a non-secret idempotency_key are required. Same key/body replays for 24 hours, including after credential rotation. Changed bodies, expired keys and deleted connections conflict. Keep credentials in a caller-side secret manager, never prompts or command history. REST only.
Server setup requirements
Reason’s hosted connector initiates SSH from Reason’s cloud, not from your browser or computer. Configure the server before saving a connection:
- Use a Linux server with an x64 or arm64 CPU. macOS and Windows are not supported by this automatic installation flow. SSH devices run headlessly; they do not provide desktop streaming or interactive desktop control.
- Provide a public hostname or IP address and an SSH port reachable from Reason’s cloud. Private LAN, localhost, and Tailscale-only addresses are not supported. A successful SSH connection from your own computer does not prove cloud reachability. Configure the server’s firewall and any upstream network rules according to your organization’s security policy; do not disable the firewall.
- Enable SSH and authorize the supplied SSH user with a password or private key. Prefer a dedicated, least-privileged account and key. The account needs a private, writable home directory, disk space for the worker, and permission to read, write, and execute within its workspace. Root access is not required.
- Allow outbound HTTPS for the CLI download and the worker’s connection to Reason. The host needs bash, curl, getent, stat, readlink, and timeout. With systemd, a non-root user needs lingering enabled by the administrator. Without systemd, use Reconnect after the host restarts or the worker stops.
- Save the connection, then connect to probe the SSH host key. Compare the fingerprint with a trusted source before choosing Trust & install. Reason installs and enrolls the worker only after that verification.
If setup fails, inspect the connection’s error_code: ssh_host_blocked means the address is not permitted; ssh_dns_failed means name resolution failed; ssh_authentication_failed means the credentials were rejected; ssh_platform_unsupported means the operating system or architecture is unsupported. A connected setup receipt is not proof that the Device remains online; check its current Device status.
Scope:ssh:writecurl --request POST \
--url https://api.reasonmachines.ai/v3/organizations/{orgId}/ssh-connections \
--header 'Authorization: Bearer <token>' \
--header 'Content-Type: application/json' \
--data '
{
"name": "Build host",
"host": "host.example.com",
"username": "ubuntu",
"authentication": {
"type": "private_key",
"private_key": "<load from your secret manager>"
},
"consent_host_access": true,
"idempotency_key": "save-build-host-001"
}
'import requests
url = "https://api.reasonmachines.ai/v3/organizations/{orgId}/ssh-connections"
payload = {
"name": "Build host",
"host": "host.example.com",
"username": "ubuntu",
"authentication": {
"type": "private_key",
"private_key": "<load from your secret manager>"
},
"consent_host_access": True,
"idempotency_key": "save-build-host-001"
}
headers = {
"Authorization": "Bearer <token>",
"Content-Type": "application/json"
}
response = requests.post(url, json=payload, headers=headers)
print(response.text)const options = {
method: 'POST',
headers: {Authorization: 'Bearer <token>', 'Content-Type': 'application/json'},
body: JSON.stringify({
name: 'Build host',
host: 'host.example.com',
username: 'ubuntu',
authentication: {type: 'private_key', private_key: '<load from your secret manager>'},
consent_host_access: true,
idempotency_key: 'save-build-host-001'
})
};
fetch('https://api.reasonmachines.ai/v3/organizations/{orgId}/ssh-connections', options)
.then(res => res.json())
.then(res => console.log(res))
.catch(err => console.error(err));<?php
$curl = curl_init();
curl_setopt_array($curl, [
CURLOPT_URL => "https://api.reasonmachines.ai/v3/organizations/{orgId}/ssh-connections",
CURLOPT_RETURNTRANSFER => true,
CURLOPT_ENCODING => "",
CURLOPT_MAXREDIRS => 10,
CURLOPT_TIMEOUT => 30,
CURLOPT_HTTP_VERSION => CURL_HTTP_VERSION_1_1,
CURLOPT_CUSTOMREQUEST => "POST",
CURLOPT_POSTFIELDS => json_encode([
'name' => 'Build host',
'host' => 'host.example.com',
'username' => 'ubuntu',
'authentication' => [
'type' => 'private_key',
'private_key' => '<load from your secret manager>'
],
'consent_host_access' => true,
'idempotency_key' => 'save-build-host-001'
]),
CURLOPT_HTTPHEADER => [
"Authorization: Bearer <token>",
"Content-Type: application/json"
],
]);
$response = curl_exec($curl);
$err = curl_error($curl);
curl_close($curl);
if ($err) {
echo "cURL Error #:" . $err;
} else {
echo $response;
}package main
import (
"fmt"
"strings"
"net/http"
"io"
)
func main() {
url := "https://api.reasonmachines.ai/v3/organizations/{orgId}/ssh-connections"
payload := strings.NewReader("{\n \"name\": \"Build host\",\n \"host\": \"host.example.com\",\n \"username\": \"ubuntu\",\n \"authentication\": {\n \"type\": \"private_key\",\n \"private_key\": \"<load from your secret manager>\"\n },\n \"consent_host_access\": true,\n \"idempotency_key\": \"save-build-host-001\"\n}")
req, _ := http.NewRequest("POST", url, payload)
req.Header.Add("Authorization", "Bearer <token>")
req.Header.Add("Content-Type", "application/json")
res, _ := http.DefaultClient.Do(req)
defer res.Body.Close()
body, _ := io.ReadAll(res.Body)
fmt.Println(string(body))
}HttpResponse<String> response = Unirest.post("https://api.reasonmachines.ai/v3/organizations/{orgId}/ssh-connections")
.header("Authorization", "Bearer <token>")
.header("Content-Type", "application/json")
.body("{\n \"name\": \"Build host\",\n \"host\": \"host.example.com\",\n \"username\": \"ubuntu\",\n \"authentication\": {\n \"type\": \"private_key\",\n \"private_key\": \"<load from your secret manager>\"\n },\n \"consent_host_access\": true,\n \"idempotency_key\": \"save-build-host-001\"\n}")
.asString();require 'uri'
require 'net/http'
url = URI("https://api.reasonmachines.ai/v3/organizations/{orgId}/ssh-connections")
http = Net::HTTP.new(url.host, url.port)
http.use_ssl = true
request = Net::HTTP::Post.new(url)
request["Authorization"] = 'Bearer <token>'
request["Content-Type"] = 'application/json'
request.body = "{\n \"name\": \"Build host\",\n \"host\": \"host.example.com\",\n \"username\": \"ubuntu\",\n \"authentication\": {\n \"type\": \"private_key\",\n \"private_key\": \"<load from your secret manager>\"\n },\n \"consent_host_access\": true,\n \"idempotency_key\": \"save-build-host-001\"\n}"
response = http.request(request)
puts response.read_body{
"connection": {
"id": "3c90c3cc-0d44-4b50-8888-8dd25736052a",
"name": "<string>",
"host": "<string>",
"port": 123,
"username": "<string>",
"workspace_path": "<string>",
"auth_type": "password",
"host_key_fingerprint": "<string>",
"pending_host_key_fingerprint": "<string>",
"device_id": "<string>",
"status": "saved",
"error_code": "<string>",
"created_at": "<string>",
"updated_at": "<string>"
},
"replayed": true
}{
"connection": {
"id": "3c90c3cc-0d44-4b50-8888-8dd25736052a",
"name": "<string>",
"host": "<string>",
"port": 123,
"username": "<string>",
"workspace_path": "<string>",
"auth_type": "password",
"host_key_fingerprint": "<string>",
"pending_host_key_fingerprint": "<string>",
"device_id": "<string>",
"status": "saved",
"error_code": "<string>",
"created_at": "<string>",
"updated_at": "<string>"
},
"replayed": true
}Authorizations
Your Reason API key from Settings > API. New keys use reason_; legacy ara_ keys remain accepted. Keys are capability-scoped: run, mcp:read, mcp:write, secrets:read, secrets:write, sessions:read, sessions:debug, knowledge:read, memory:read, memory:write, skills:read, skills:write, repos:read, repos:write, reviews:read, reviews:write, deployment:read, analytics:read, org:read, org:write, attachments:read, attachments:write, guardrails:read, guardrails:write, automations:read, automations:write, agent_auth:read. mcp:write manages MCP server configuration only; it does not authorize remote MCP-tool execution. sessions:debug is privileged: it expands diagnostic session events only for organization owners/admins.
Path Parameters
Organization id or slug. Resolve it with GET /v3/self.
Body
1 - 1601 - 253^[a-zA-Z0-9.:_-]+$SSH username, up to 64 characters, or Railway's sbx:: routing selector.
1 - 77- Option 1
- Option 2
Show child attributes
Show child attributes
Persist one non-secret key per logical request. Retry the identical body/key for up to 24 hours. Expired or deleted request keys return 409; reconnect intentionally with a fresh key.
1 - 160^[A-Za-z0-9._:-]+$1 <= x <= 65535Optional absolute remote workspace path. Omit to use the installer's private default workspace.
1 - 1024
