This file records the exe.dev API documentation that this repository depends on. It was last checked on 2026-07-10 from these source pages:
- https://exe.dev/docs/api
- https://exe.dev/docs/login-with-exe
- https://exe.dev/docs/https-api
- https://exe.dev/docs/https-api-local-key
- https://exe.dev/docs/https-tokens-for-vms
Each page is also available as raw markdown by appending .md to the URL,
for example https://exe.dev/docs/https-api.md. The full command list lives
in the CLI reference at https://exe.dev/docs/section/10-cli-reference.
exe.dev exposes two programmatic access styles:
-
SSH command automation (the primary API):
ssh exe.dev ls --json ssh exe.dev new --json
-
HTTPS command automation:
POST https://exe.dev/exec
POST /exec is not a resource-style REST API. The request body is the same
exe.dev command that would be typed into ssh exe.dev or run through
ssh exe.dev <command>. API responses enable JSON output by default, equivalent
to passing --json.
Example ls --json VM object shape:
{
"https_url": "https://bloggy.exe.xyz",
"region": "lon",
"region_display": "London, UK",
"ssh_dest": "bloggy.exe.xyz",
"status": "running",
"vm_name": "bloggy"
}Minimal HTTPS request:
curl -X POST https://exe.dev/exec \
-H "Authorization: Bearer $EXE_DEV_API_KEY" \
-d 'whoami'Operational limits:
- Only
POSTis accepted. - There is no stdin and no pty.
- Request bodies are limited to 64KB.
- Commands time out after 30 seconds.
- Commands that require interactive input do not fit this API.
Any command can be introspected without side effects by passing --help
(for example new --help), which returns its flags and examples as JSON.
The exe.dev HTTPS API uses bearer tokens. A token can be generated by exe.dev:
ssh exe.dev ssh-key generate-api-key --exp=30dTokens can also be created locally by signing permissions JSON with an SSH private key. For automation, prefer a dedicated SSH key so the API token family can be revoked without disrupting normal user SSH access.
ssh-keygen -t ed25519 -C api -f ~/.ssh/exe_dev_api
cat ~/.ssh/exe_dev_api.pub | ssh exe.dev ssh-key addLocal token creation flow:
b64url() { tr -d '\n=' | tr '+/' '-_'; }
export PERMISSIONS='{}'
export PAYLOAD=$(printf '%s' "$PERMISSIONS" | base64 | b64url)
export SIG=$(printf '%s' "$PERMISSIONS" | ssh-keygen -Y sign -f ~/.ssh/exe_dev_api -n v0@exe.dev)
export SIGBLOB=$(echo "$SIG" | sed '1d;$d' | b64url)
export EXE_DEV_API_KEY="exe0.$PAYLOAD.$SIGBLOB"PERMISSIONS is embedded in the token as plaintext JSON. Do not put secrets in
it.
Supported top-level permission fields:
exp: UTC Unix timestamp after which the token is invalid. The default is the distant future (never expires); exe.dev strongly recommends always settingexp.nbf: UTC Unix timestamp before which the token is not valid yet.cmds: exe.dev command names this token may run.cmdscontrols command names only; flags, arguments, and options (like--json) are always allowed when the base command is permitted.ctx: signed JSON context uninterpreted by exe.dev. Must itself be valid JSON complying with the restrictions below.
The empty object {} uses defaults. For least-privilege automation, specify
cmds explicitly. Subcommands must be granted explicitly as a single string
such as "ssh-key list"; allowing ssh-key does not allow ssh-key list.
The default cmds grant is:
helplsnewwhoamissh-key listshare showexe0-to-exe1teamteam members
Commands such as rm, rename, restart, share port, share set-public,
and share set-private should be added only when automation needs them.
Permission JSON restrictions (also enforced inside ctx):
- Compact JSON is recommended (pipe through
jq -c). - No leading/trailing whitespace.
- No newlines or null bytes.
- No duplicate keys, at any level.
- Only
exp,nbf,cmds, andctxare allowed at the top level. expandnbfmust be plain integers (no decimals, no exponents) between 2000-01-01 (946684800) and 2100-01-01 (4102444800) UTC.- The whole token must not exceed 8KB.
There is no built-in replay protection (no nonce or jti). Use short-lived
tokens (small exp) to limit the replay window, and separate SSH keys per
token family for revocability.
Short opaque exe1 tokens can be issued from a valid exe0 token:
ssh exe.dev exe0-to-exe1 "$EXE_DEV_API_KEY"For a VM-scoped token, validate the source token against the VM:
ssh exe.dev exe0-to-exe1 --vm=vm-name "$EXE_DEV_API_KEY"exe1 tokens work everywhere exe0 tokens work, in exactly the same way. An
exe1 token is validated through its underlying exe0 token on every use; to
revoke an exe1 token, revoke the underlying exe0 token.
Common responses from POST /exec:
400: request body is empty, missing, or has invalid command syntax (e.g. unbalanced quotes).401: token is malformed, expired, signed by an unknown key, or fails signature verification.403: token permissions do not allow the requested command.404: unknown exe.dev command; checkssh exe.dev helpfor the full list.405: non-POST method.413: request body exceeds 64KB.422: command ran and returned a non-zero exit code; the body contains the error message.429: per-key rate limit; use separate SSH keys for independent workloads.500: unexpected server-side error.504: command exceeded the 30-second timeout.
Debug checklist for 401:
- Confirm the signing public key is listed with
ssh exe.dev ssh-key list. - Confirm
expis not in the past. - Confirm the exact compact JSON payload was signed byte-for-byte; avoid editors that add trailing newlines.
- Use a private key file with
ssh-keygen -Y sign -f; an agent-only key is not enough for this command. If the key only lives inssh-agent, export the public part withssh-add -Land use the private key file directly.
Debug checklist for 403:
- Decode the base64url payload and inspect
cmds. - Remember that subcommands are exact command-name grants.
- Add only the missing command, then regenerate the token.
The exe.dev HTTPS auth proxy can accept bearer tokens for programmatic access to
individual VM HTTPS endpoints (API servers, git push over HTTPS). This is
separate from authenticating to POST https://exe.dev/exec.
Generate a VM token through exe.dev:
ssh exe.dev ssh-key generate-api-key --vm=my-vm --label=deployGenerate a VM token locally by changing the SSH signing namespace from
v0@exe.dev to v0@VMNAME.exe.xyz:
export SIG=$(printf '%s' "$PERMISSIONS" | ssh-keygen -Y sign -f ~/.ssh/exe_dev_api -n v0@myvm.exe.xyz)VM token differences:
- The signing namespace scopes the token to one VM.
- The token payload
ctxis forwarded verbatim to the VM HTTP server asX-ExeDev-Token-Ctx. Its contents are signed, so the VM server can use them for its own authorization rules.
Preferred VM proxy authentication header:
X-Exedev-Authorization: Bearer <token>The proxy consumes and strips that header before forwarding to the VM.
Other supported forms:
Authorization: Bearer <token>is supported but deprecated for new integrations.- Basic auth can be used for tools such as
git; the username is ignored and the password is the token. This applies to VM proxy access, not/exec.
When authenticated by token, the VM server receives:
X-ExeDev-UserIDX-ExeDev-EmailX-ExeDev-Token-Ctx, if present in the token
Git HTTPS example:
echo "$TOKEN" > ~/.ssh/exe_dev_token
git config credential.helper '!f() { echo "password=$(cat ~/.ssh/exe_dev_token)"; }; f'
git clone https://myvm.exe.xyz/repo.gitApplications served through the exe.dev HTTP proxy can use exe.dev authentication instead of managing their own login system. This is complementary with sharing: public sites can bounce users through the login URL to require an email address, while private sites always carry the authentication headers because access requires the site to have been shared.
When a request is authenticated by exe.dev, the proxy adds:
X-ExeDev-UserID: stable exe.dev user identifier.X-ExeDev-Email: authenticated user email address.
For public sites, unauthenticated requests may not include these headers. For private sites, access requires authentication, so the headers are expected.
Special URLs:
https://vmname.exe.xyz/__exe.dev/login?redirect={path}redirects the user to log in, then back to{path}.POST https://vmname.exe.xyz/__exe.dev/logoutlogs the user out, removing the cookie for that domain.
Local development can simulate these headers with a reverse proxy, for example:
mitmdump \
--mode reverse:http://localhost:8000 \
--listen-port 3000 \
--set modify_headers='/~q/X-Exedev-Email/user@example.com' \
--set modify_headers='/~q/X-Exedev-Userid/usr1234'Server-side authorization can gate on X-ExeDev-Email directly; the upstream
docs include an nginx example that returns 403 unless
$http_x_exedev_email matches an allowlist.
VM services must not trust user-controlled copies of these headers when traffic can bypass the exe.dev proxy. Bind private services to localhost or firewall direct VM ports if the header values are part of authorization.
This repository uses the exe.dev HTTPS command API through
EXE_DEV_API_KEY for non-interactive VM management:
exedev-ctlwraps the exe.dev command surface, includingls,new,rm,restart,rename,tag,comment,stat,cp,resize,share(show, port, set-public, set-private, add, remove, add-link, remove-link, receive-email, access),domain(add, ls, rm),team,invite,whoami,ssh-key,set-region,integrations,billing,shelley,browser, and rawexec.exedev-k8suses exe.dev VM commands as the infrastructure layer for k3s fleet bootstrapping; against exe.dev itself it only needsls,new, andrm(node provisioning happens over direct SSH to the VMs).
For local scripts and manual debugging, prefer:
ssh exe.dev <command> --jsonFor repo automation and services, prefer:
curl -X POST https://exe.dev/exec \
-H "Authorization: Bearer $EXE_DEV_API_KEY" \
-d '<command>'For new automation tokens, grant only the commands that the workflow needs. A
minimal exedev-k8s fleet token needs:
{"cmds":["ls","new","rm"],"exp":1798761600}A broader VM lifecycle token for exedev-ctl workflows typically adds:
{
"cmds": [
"ls",
"new",
"rm",
"restart",
"rename",
"whoami",
"share show",
"share port",
"share set-public",
"share set-private",
"domain ls"
],
"exp": 1798761600
}Use shorter-lived tokens for CI and long-running automation where replay risk is important.