HeavenDocs

Getting started

Every request carries an API key, a signed token or a sign-in in its Authorization header.

API keys

Send a key as a bearer token on every request:

Header
Authorization: Bearer heaven1.…

Create one over SSH, with any SSH key you've added on the dashboard:

Terminal
ssh heaven.dev api-key create --label deploy --expires 30d \
  --command view --command restart --command "'exec my-site'"

Or create one with the API, using a credential that can create keys:

Request
curl https://heaven.dev/v1/account/modify \
  -H "Authorization: Bearer $HEAVEN_API_KEY" \
  --json '{"action":"api-key create","label":"deploy","expires":"30d","commands":["view","restart","exec my-site"]}'
200
{
  "version": 1,
  "data": {
    "id": "51d8138f-4472-4ec4-b4f8-1ec1d2ba65a2",
    "label": "deploy",
    "cmds": ["view", "restart", "exec my-site"],
    "created_at": "2026-11-01T23:18:35Z",
    "expires_at": "2026-12-01T23:18:35Z",
    "last_used_at": null,
    "revoked_at": null,
    "token": "heaven1.…"
  }
}

What a key can run

A key can run only the commands its commands list names. Each endpoint's reference gives the command for every read and action, such as create, restart, pool view or ssh-key add.

Some commands can't be given to API keys you create. These commands give out access or spend money, so they could get around a key's limits. A key is refused them even if its commands name them. Use a sign-in key or SSH instead.

What a key can reach

A scope limits a key to some pools, Computers and tags, whatever its commands allow:

Request
curl https://heaven.dev/v1/account/modify \
  -H "Authorization: Bearer $HEAVEN_API_KEY" \
  --json '{"action":"api-key create","label":"web-deploy","expires":"30d","scope":{"pools":["web"]}}'

Sign in from a program

A program, a script or an agent can sign in with a short code that you approve in your browser. Nothing needs copying back, so it works anywhere, even inside a Computer. The key it gets acts as you for 30 days. heaven login does all of this for you and keeps the key.

  1. Start the sign-in

    Request
    curl https://heaven.dev/v1/auth/create \
      --json '{"method":"device","label":"laptop"}'
    200
    {
      "version": 1,
      "data": {
        "device_code": "…",
        "user_code": "BCDF-GHJK",
        "verification_uri": "https://heaven.dev/device",
        "expires_in": 900,
        "interval": 5
      }
    }

    Keep device_code to yourself. Show user_code and verification_uri. To offer the machine's SSH key as well, add "ssh_public_key". The approval page adds it to your account if you leave its box ticked.

  2. Approve it in the browser

    Open https://heaven.dev/device, sign in if needed, type the code and approve. Deny a code you didn't start.

  3. Collect the key

    Check every interval seconds until it's approved:

    Request
    curl https://heaven.dev/v1/auth/create \
      --json '{"method":"device_code","device_code":"DEVICE_CODE"}'
    200
    {"version": 1, "data": {"state": "pending"}}
    
    {"version": 1, "data": {"state": "approved", "api_key": {"id": "…", "label": "laptop", "expires_at": "…", "token": "heaven1.…"}}}

    Checking sooner than interval returns rate_limited. A denied sign-in returns denied. A sign-in expires after 15 minutes and then returns not_found, so start again.

A sign-in key can run every command you can, including those that API keys you create can't. It's listed with your API keys under its label, and api-key remove ends it.

Signed SSH tokens

If your account has an SSH key, you don't need a stored API key at all. Sign a short-lived heaven0 token on your own machine instead. Removing the SSH key ends every token it signed.

Sign a token with OpenSSH
Terminal
b64url() { tr -d '\n=' | tr '+/' '-_'; }
PAYLOAD=$(python3 -c 'import json,time; print(json.dumps({"exp":int(time.time())+3600},separators=(",",":")),end="")')
PAYLOAD64=$(printf '%s' "$PAYLOAD" | base64 | b64url)
SIGNATURE=$(printf '%s' "$PAYLOAD" | ssh-keygen -Y sign -f ~/.ssh/id_ed25519 -n v0@heaven.dev)
SIG64=$(printf '%s\n' "$SIGNATURE" | sed '1d;$d' | b64url)
TOKEN="heaven0.$PAYLOAD64.$SIG64"
curl https://heaven.dev/v1/account/ -H "Authorization: Bearer $TOKEN"

The payload needs exp, in Unix seconds. To limit the token as an API key's commands do, add "cmds":[…]. Without it, the token gets the read-only set. An SSH key limited to a tag or to one Computer can't sign these tokens.

Tokens for a private site

A program such as Git, curl or a deploy job opens a private site, https://NAME.heaven.dev, with a token for that Computer. This command makes one that lasts until you remove it:

Terminal
ssh heaven.dev ssh-key create --computer NAME --label deploy

The token is shown once. Heaven signs it with a new SSH key limited to that Computer, lists the key in ssh-key view, and keeps no copy of the token. ssh-key remove revokes it.

You can also sign a token yourself, with the same recipe as above and the namespace v0@NAME.heaven.dev. A token opens what your account can open there: the main address with web access, and every port with ownership or root access. Send it as X-Heaven-Authorization: Bearer TOKEN, or as the password for HTTPS Basic authentication, which Git uses. The username is ignored.

Sign a site token with your own key
Terminal
b64url() { tr -d '\n=' | tr '+/' '-_'; }
PAYLOAD=$(python3 -c 'import json,time; print(json.dumps({"exp":int(time.time())+3600},separators=(",",":")),end="")')
PAYLOAD64=$(printf '%s' "$PAYLOAD" | base64 | b64url)
SIGNATURE=$(printf '%s' "$PAYLOAD" | ssh-keygen -Y sign -f ~/.ssh/id_ed25519 -n v0@NAME.heaven.dev)
SIG64=$(printf '%s\n' "$SIGNATURE" | sed '1d;$d' | b64url)
TOKEN="heaven0.$PAYLOAD64.$SIG64"
curl https://NAME.heaven.dev/ -H "X-Heaven-Authorization: Bearer $TOKEN"

An SSH key limited to one Computer (ssh-key add --computer) signs tokens for that Computer only. A key limited to a tag can't sign them. Renaming the Computer ends its tokens.

Signing out

POST /v1/auth/delete ends a website sign-in and returns {"logged_out": true}. To end an API key, including a sign-in key, use api-key remove. heaven logout removes the program's own key.

Keeping keys safe