Configure box SSH key
Adds an OpenSSH public key to the running Box so the caller can SSH as user user. The response carries machineIp, which is the host to connect to on port 22.
This is also how you reach your own machine from inside a Box without the CLI: authorize the key, then open a reverse tunnel with stock OpenSSH, and a port on your machine answers at 127.0.0.1:<port> inside the Box for as long as the ssh process runs.
ssh -o ExitOnForwardFailure=yes -i ~/.ssh/id_ed25519 -N -R 7777:127.0.0.1:7777 user@<machineIp>
box forward <id> --reverse --local 7777 is the same tunnel with the key handling and redial done for you.
curl --request POST \
--url https://ascii.dev/api/box/v1/boxes/{boxId}/sshkey \
--header 'Authorization: Bearer <token>' \
--header 'Content-Type: application/json' \
--data '
{
"key": "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAA... user@host"
}
'import requests
url = "https://ascii.dev/api/box/v1/boxes/{boxId}/sshkey"
payload = { "key": "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAA... user@host" }
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({key: 'ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAA... user@host'})
};
fetch('https://ascii.dev/api/box/v1/boxes/{boxId}/sshkey', 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://ascii.dev/api/box/v1/boxes/{boxId}/sshkey",
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([
'key' => 'ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAA... user@host'
]),
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://ascii.dev/api/box/v1/boxes/{boxId}/sshkey"
payload := strings.NewReader("{\n \"key\": \"ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAA... user@host\"\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://ascii.dev/api/box/v1/boxes/{boxId}/sshkey")
.header("Authorization", "Bearer <token>")
.header("Content-Type", "application/json")
.body("{\n \"key\": \"ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAA... user@host\"\n}")
.asString();require 'uri'
require 'net/http'
url = URI("https://ascii.dev/api/box/v1/boxes/{boxId}/sshkey")
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 \"key\": \"ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAA... user@host\"\n}"
response = http.request(request)
puts response.read_body{
"ok": true,
"type": "ssh_key.configured",
"success": true,
"machineIp": "203.0.113.10",
"sshUser": "user"
}{
"ok": false,
"type": "box.error",
"status": 400,
"code": "invalid_json",
"message": "Request body must be valid JSON.",
"error": {
"code": "invalid_json",
"message": "Request body must be valid JSON.",
"status": 400
},
"requestId": "req_01HX..."
}{
"ok": false,
"type": "box.error",
"status": 401,
"code": "unauthorized",
"message": "Unauthorized",
"error": {
"code": "unauthorized",
"message": "Unauthorized",
"status": 401
},
"requestId": "req_01HX..."
}{
"ok": false,
"type": "box.error",
"status": 409,
"code": "provider_not_configured",
"message": "Prompting is locked until Codex is configured on the Agents page.",
"requestId": "req_01HX...",
"error": {
"code": "<string>",
"message": "<string>",
"status": 123,
"details": {}
}
}{
"ok": false,
"type": "box.error",
"status": 409,
"code": "provider_not_configured",
"message": "Prompting is locked until Codex is configured on the Agents page.",
"requestId": "req_01HX...",
"error": {
"code": "<string>",
"message": "<string>",
"status": 123,
"details": {}
}
}Authorizations
Box bearer token in the form box_.... Service API keys authenticate Box operations.
Path Parameters
Public Box id returned by create/list/get box calls.
^bx_[23456789abcdefghjkmnpqrstuvwxyz]{8}$Body
Public SSH key in OpenSSH format. Private keys are rejected.
"ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAA... user@host"
curl --request POST \
--url https://ascii.dev/api/box/v1/boxes/{boxId}/sshkey \
--header 'Authorization: Bearer <token>' \
--header 'Content-Type: application/json' \
--data '
{
"key": "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAA... user@host"
}
'import requests
url = "https://ascii.dev/api/box/v1/boxes/{boxId}/sshkey"
payload = { "key": "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAA... user@host" }
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({key: 'ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAA... user@host'})
};
fetch('https://ascii.dev/api/box/v1/boxes/{boxId}/sshkey', 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://ascii.dev/api/box/v1/boxes/{boxId}/sshkey",
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([
'key' => 'ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAA... user@host'
]),
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://ascii.dev/api/box/v1/boxes/{boxId}/sshkey"
payload := strings.NewReader("{\n \"key\": \"ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAA... user@host\"\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://ascii.dev/api/box/v1/boxes/{boxId}/sshkey")
.header("Authorization", "Bearer <token>")
.header("Content-Type", "application/json")
.body("{\n \"key\": \"ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAA... user@host\"\n}")
.asString();require 'uri'
require 'net/http'
url = URI("https://ascii.dev/api/box/v1/boxes/{boxId}/sshkey")
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 \"key\": \"ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAA... user@host\"\n}"
response = http.request(request)
puts response.read_body{
"ok": true,
"type": "ssh_key.configured",
"success": true,
"machineIp": "203.0.113.10",
"sshUser": "user"
}{
"ok": false,
"type": "box.error",
"status": 400,
"code": "invalid_json",
"message": "Request body must be valid JSON.",
"error": {
"code": "invalid_json",
"message": "Request body must be valid JSON.",
"status": 400
},
"requestId": "req_01HX..."
}{
"ok": false,
"type": "box.error",
"status": 401,
"code": "unauthorized",
"message": "Unauthorized",
"error": {
"code": "unauthorized",
"message": "Unauthorized",
"status": 401
},
"requestId": "req_01HX..."
}{
"ok": false,
"type": "box.error",
"status": 409,
"code": "provider_not_configured",
"message": "Prompting is locked until Codex is configured on the Agents page.",
"requestId": "req_01HX...",
"error": {
"code": "<string>",
"message": "<string>",
"status": 123,
"details": {}
}
}{
"ok": false,
"type": "box.error",
"status": 409,
"code": "provider_not_configured",
"message": "Prompting is locked until Codex is configured on the Agents page.",
"requestId": "req_01HX...",
"error": {
"code": "<string>",
"message": "<string>",
"status": 123,
"details": {}
}
}