← All docs

HTTPS API

The HTTPS API enables programmatic HTTPS access both to exe.dev and to individual VMs.

exe.dev

The exe.dev HTTPS API is nothing but the SSH API shoved into a POST body.

This might seem crazy, but it means you have only one API to learn, and you can develop and debug all your API calls interactively over SSH.

All requests use the same endpoint:

POST https://exe.dev/exec

The POST body is the ssh command to run, exactly as if it were typed into the REPL or exec'd via ssh. JSON output is always enabled for API responses (equivalent to --json). The returned body is the ssh output. That's it.

See the CLI reference for the full list of available commands.

Authentication

Authentication uses bearer tokens. To have exe.dev generate a token, run:

ssh exe.dev ssh-key generate-api-key --exp=30d

Alternatively, you can generate bearer tokens locally using your SSH key. See HTTPS API Local Key Creation for details.

For programmatic access to VMs, see HTTPS Tokens for VMs.

Example

After generating a token as described above, run commands by passing it as a bearer token:

curl -X POST https://exe.dev/exec \
     -H "Authorization: Bearer exe1.AAA" \
     -d whoami

Token details

Granular permissions

Token permissions are specified using (signed) JSON. The empty object {} gives you the defaults, and each field you add overrides a default.

The permissions JSON is public and embedded as plaintext in your token. Do not put secrets in it.

Available fields:

  • exp: specifies an integer UTC unix timestamp after which the token is no longer valid. For example, {"exp":1922918400} means this token cannot be used after Dec 5, 2030. The default exp is the distant future, that is, it never expires. We strongly recommend always setting exp.

  • nbf: specifies a UTC unix timestamp before which the token is not yet valid. For example, {"nbf": 1922918400} means this token cannot be used until Dec 5, 2030. The default nbf is the distant past.

  • cmds: specifies which exe.dev commands this token can execute. Subcommands are specified as a single string, such as "ssh-key list". Including a parent command like "ssh-key" does not grant access to its subcommands. Flags, arguments, and options (like --json) are always allowed when the base command is permitted; cmds controls command names only. The default cmds is ["help","ls","new","whoami","ssh-key list","share show","exe0-to-exe1"].

  • ctx: uninterpreted by exe.dev. Can be used to differentiate otherwise-identical tokens, or to pass data to your VM server (see HTTPS Tokens for VMs). Must contain valid JSON that complies with the restrictions in the next section.

Need a new type of permission? Let us know: support@exe.dev or Discord.

JSON recommendations and restrictions

We recommend compacting the JSON to keep tokens short: remove all whitespace, or pipe through jq -c.

There are a few JSON restrictions, including inside ctx, for good security hygiene.

  • No leading or trailing whitespace.
  • No newlines (\n, \r).
  • No null bytes.
  • No duplicate keys, at any level.
  • Known fields only: Only exp, nbf, cmds, and ctx are allowed at the top level.
  • Integers: exp and nbf must be integers. No decimals like 2000000000.0, no exponents like 2e9.
  • Timestamp range: exp and nbf must be between Jan 1, 2000 (946684800) and Jan 1, 2100 (4102444800).
  • Size limit: The entire token must not exceed 8KB.

The ctx field is passed through to your server verbatim, but we do validate its internal structure against these rules.

Troubleshooting

Invalid token (401)

The token is malformed, expired, signed with an unrecognized key, or the signature doesn't verify. Common causes:

  • The key used to sign the token hasn't been added to your exe.dev account. Run ssh exe.dev ssh-key list to check.
  • The token has expired (exp is in the past).
  • Whitespace or newlines in the permissions JSON. The payload must be byte-for-byte identical to what was signed. Pipe through jq -c to compact, and avoid editors that add trailing newlines.
  • Using ssh-agent instead of a key file. ssh-keygen -Y sign requires -f path/to/key. If your key is only in the agent, export it first: ssh-add -L | grep "your-key-comment" > /tmp/key.pub, then use the private key file directly.

Bad request (400)

The request body is empty, missing, or has invalid command syntax (e.g., unbalanced quotes).

Command not allowed by token permissions (403)

The token's cmds list doesn't include the command you're trying to run. The token payload is base64url-encoded and can be decoded to inspect its contents.

Subcommands must be listed explicitly. Including "ssh-key" does not grant access to "ssh-key list".

Unknown command (404)

The command doesn't exist. Check ssh exe.dev help for the full list of available commands.

Method not allowed (405)

Only POST is accepted. You sent a GET, PUT, or other HTTP method.

Request too large (413)

The request body exceeds the 64KB limit.

Command failed (422)

The command ran but returned a non-zero exit code (e.g., missing arguments, invalid input). The body contains the error message.

Timeout (504)

The command took longer than 30 seconds to execute.

Rate limited (429)

Too many requests from this SSH key. The limit is per-key: use separate SSH keys for independent workloads.

Internal error (500)

Something unexpected went wrong server-side. If this persists, contact support@exe.dev.

FAQ

Is there replay protection? There is no built-in nonce or jti mechanism. Use short-lived tokens (small exp) to limit the replay window. Use separate ssh keys for sets of API keys for revocability.

Can I introspect a command without side effects? Yes. Pass --help to any command (e.g., new --help) to get its flags and examples as JSON.

What are the /exec limitations? The API has no stdin, no pty, and a 30-second timeout (HTTP 504 on timeout). Commands that require interactive input won't work. If it hurts, don't do it. The request body limit is 64KB.