# API keys & sandboxes

> An API key is your OpenEng key: the credential the openeng engine starts with (--key or OPENENG_KEY), and the one headless and sandbox machines use. Sandboxes are meant to register machines as a managed fleet that pulls work from your account; the current engine can't register a node yet. The two pages, Deploy → API keys and Deploy → Sandboxes, are two views of the same thing: which machines may work for your account, and which one is live. As of September 25, 2026, OpenEng AI isn't live yet.

An API key is your **OpenEng key**: the credential the `openeng` engine starts
with, and the one headless and sandbox machines use. Sandboxes are meant to
register machines as a managed fleet that pulls work from your account through
the Console; the current engine can't register a node yet. The two pages (Deploy → API keys, Deploy → Sandboxes) are two views of
the same thing: **which machines may work for your account, and which one is
live**.

> [!WARNING] Status on September 25, 2026: OpenEng AI isn't live yet
> The Console and Web Console interfaces load, but the OpenEng AI API routes and the Broker aren't
> deployed. Nothing can be saved or published, and no AI engine can register or connect. Pro,
> Enterprise and pay-as-you-go launch in Q4 2026 and can't be purchased yet. This page describes
> OpenEng AI as it works once those services are live ([status](ai.md)).

*Figure: Deploy → API keys and Deploy → Sandboxes — issue return-once keys, bind each to a node, and run the fleet table with its one-active rule.*

## API keys

### What they are

An API key (`OPENENG_KEY`) is the credential the engine starts with: pass it
as `--key=<key>` or set the `OPENENG_KEY` environment variable
([start the engine](engine.md#start)). The engine registers the key with the
platform, which checks the key and your plan and ties the key to that one
running engine. The same key authenticates **headless** and **sandbox-mode**
machines — the credential that lets unattended nodes pull your configs and
work. Issue one per machine-job (`laptop`, `ci-runner`, `nightly-box`).

### Issuing — and the return-once rule

**Issue key** opens a panel with two fields:

- **Label** — what the key is for (e.g. the machine it lives on).
- **Scopes** — optional, comma-separated; empty means full account scope.
  A scope has the form `<resource>:<action>`, such as `config:read`, and the
  server refuses anything else. Scopes are recorded but not enforced yet, so
  every key currently has full account authority.

The next screen shows the key value **exactly once**. Copy it now and set it
as `OPENENG_KEY` on the target machine. The server stores only a hash —
once the panel closes, the value can never be displayed again, only revoked
and reissued. (The page drops it from memory the moment you close.) A key
expires one year after it's issued.

```bash
export OPENENG_KEY="oeng_…"     # on the machine that will use it
openeng serve                    # or: openeng serve --key="oeng_…"
```

At your plan's key limit, **Issue key** is disabled and its tooltip gives the
reason (for example *Pro includes 1 API key — you're using 1*); a meter next
to it shows keys used against the limit.

### Keys, engines and machines

A key authenticates, but machines are still counted
([Plans & billing](account.md#engines-and-devices)):

- **One engine per key.** The platform ties each key to one running engine.
  Starting a second engine with the same key fails with
  `this key already runs an engine — stop it or generate a new key`. Pro
  includes one key, so one engine at a time.
- **Headless machines** (no terminal — CI, servers) take a **headless machine
  slot**: Free 0 · Pro 1 · Enterprise unlimited, or a number set on the
  account. The platform counts headless runners holding a live lease; the
  current engine's `openeng headless` only checks the key and exits
  ([headless machines](engine.md#headless)).
- **Sandbox mode** — nodes register freely; *active* nodes are limited
  ([below](#the-one-active-rule-pro)).

Below the key cards, **Active engine sessions** lists the engines signed in on
your machines, with their last heartbeat.

### Key cards

Each non-revoked key shows: label · mono key id · **Created** · **Last
used** (an em-dash until the stamp populates) · **Scopes** chips (or *full
account*) · and the **Bound sandbox** (or *Not bound*). Each card has
**Rebind** and **Revoke** (Revoke asks you to confirm).

### Key ↔ sandbox binding

A key can be bound to **exactly one sandbox**, and a sandbox to one key.
**Rebind** picks a new node, with the consequence named before you confirm:

> *"**node-a** will stop receiving work on its next sync. This key then
> authorizes only **node-b**."*

On Pro — where exactly one sandbox is active — rebinding the key **is** the
activation selector. A node bound to a key refuses to register under any
other key; an unbound node accepts any of your keys until one is bound.
Revoking a key stops it from registering an engine or calling the API; the
fleet page reflects it on the next sync. An engine already connected keeps its
Broker session until that session expires (up to 24 hours), so stop the engine
as well.

## Sandboxes

> [!WARNING] Sandbox nodes don't run work yet
> The current `openeng` engine build has no sandbox mode: it doesn't register
> as a node, lease work or deliver results, because the fleet runtime isn't
> part of it, and `openeng headless` only checks the key and exits
> ([the engine](engine.md#headless)). Once the OpenEng AI services are live
> ([status](ai.md)), the Sandboxes page, key binding and activation work in the
> Console on **Pro** or Enterprise, but the fleet table stays empty until a node
> can register. The page's empty state suggests `OPENENG_KEY=… openeng sandbox`;
> the engine has no `sandbox` command.

### What a sandbox node is

A machine that works for your account through the platform's relay instead
of for a person at a keyboard: the terminal is replaced by the platform's work
queue. The relay registers the node, hands it **one job at a time** on its
sync, and records what it reports back; results go to a
[storage point](storage-points.md). Only the **active** node receives work;
inactive nodes keep syncing but never lease work.

### Registering a node

Registration happens on the node; this page manages the fleet. Sandbox mode
is **API-key-only** (no interactive auth, ever). When a node registers, the
platform checks, in order — each failure is an explicit refusal, never a
degraded run:

1. The key is valid, and if the node is already bound to a key, it's that
   key (*This sandbox is bound to a different API key*).
2. The account is on **Pro** (or Enterprise): *Sandbox mode requires the Pro
   plan*.
3. The active-node limit. A node that would exceed it is still registered,
   inactive, so you can activate it here.

The same machine re-registering keeps its node id, activation state and key
binding. A one-machine Pro fleet doesn't stall: its first node becomes active
automatically when no other node is.

### The fleet table

Columns: **Node** · **Active** · **Status** (online means seen within the
last 5 minutes) · **Last seen** · **Bound key** · **Config** · **Alloc
(c/g/m)** · **Version**. A meter at the top shows active nodes against your
limit. Expanding a row opens the node detail:

- **Capability profile** — the hardware/models/capabilities document the
  node reported ("Reported at next sync" until it has).
- **Resource allocation** — CPU/GPU/Memory %, each clamped **1–100** (zero
  would deadlock job admission). Saved allocations apply **at the next run
  boundary**, never mid-run.
- **Run history** — the relay's run records for this node (state,
  timestamps, consumed tokens where reported).
- **Revoke** — the node gets a refusal on its next sync and shuts down.

Below the table, **Active headless runners** lists the headless machines
holding a live lease, with their last heartbeat.

### The one-active rule (Pro)

Pro registers **any number** of nodes but exactly **one is active** — only
the active node receives work. The Active toggle handles the swap
explicitly: activating node B while A is active raises a confirmation naming
A ("stops receiving work on its next sync") and rebinding your key to B. The
server enforces the swap regardless of what the UI shows. Enterprise
activates any number of nodes with a plain toggle, unless a size is set on
the account.

If a fleet exists and **no** node is active, the page shows a *choose one*
banner — until you pick, the fleet receives no work. Activation requires an
API key to bind: with none issued, the toggle stops with *Issue an API key
first* and points you to the API keys page.

## Plan & tier notes

| Limit | Free | Pro | Enterprise |
|---|---|---|---|
| API keys | — | 1 | Unlimited |
| Sandbox registration | — | ✓ (any number of nodes) | ✓ |
| Active sandboxes | — | exactly 1 | Unlimited |
| Headless machines | — | 1 | Unlimited |

An Enterprise account can also have its own sizes set for keys, active
sandboxes and headless machines. Both pages show locked states on Free, with
meters (e.g. *Active 1/1*) once unlocked. The server enforces every limit
independently of the UI. Because an API key needs Pro, so does running the
engine; Pro and Enterprise launch in Q4 2026 and can't be purchased yet
([Plans & billing](account.md#ai-pricing)).

## Troubleshooting

**Lost the key value.** Unrecoverable by design (only the hash is stored).
Revoke the key and issue a new one — two clicks.

**`this key already runs an engine — stop it or generate a new key`.** Another
engine is running with this key. Stop it, or issue a second key (Enterprise)
for the second machine.

**`cannot start: no OpenEng key`.** The engine found neither `--key` nor
`OPENENG_KEY`. Export the key in the shell that starts the engine.

**The node never appears on the Sandboxes page.** Expected with the current
engine build, which doesn't register sandbox nodes (see the warning under
[Sandboxes](#sandboxes)).

**The node is online but receives no work** (once sandbox mode ships). On Pro, is it the *active*
node? Is your key bound to it (rebinding elsewhere cuts it off on next
sync)? Is the queue actually non-empty?

**"Issue an API key first" when activating.** Activation binds your key to
the node — issue a key on the API keys page, then activate.

**Allocation changes don't seem to apply.** They apply at the **next run
boundary**, never mid-run. Values are clamped to 1–100 on save.

**Revoked a node but it still shows online.** Status updates on the node's
next sync — at which point it receives its cut-off and stops.
