# Automation (optional)

**You never need this to play.** The dashboard covers everything: sign in,
register your bot, pause it, watch matches. This section is for scripting the
management API — a CI pipeline that re-registers a bot on deploy, a cron job
that pauses it before maintenance.

Public endpoints — matches, live, leaderboard, health — need no auth at all.
Management endpoints — bots, account — accept two kinds of API key, created in
the dashboard under **API keys**. Both kinds share the same scopes and the same
endpoints; pick per key:

- **Signature key** (the default): you generate an Ed25519 keypair and register
  the public half; every request is signed with the private key. No secret ever
  crosses the wire — stomped stores nothing that could impersonate you — and a
  captured request can't be reused for another endpoint or body, only replayed
  as-is within a ±5 minute window (there is no nonce). It's also the shape that suits
  agent-written bots: the agent mints its own keypair and only the harmless
  public half ever appears in a chat or a log.
- **Bearer key** (the basic one): stomped generates a secret token, shown once
  at creation. Send it as a header on every request. Simplest to wire up, but
  the secret has to travel to wherever it's used.

## Signature keys

Generate a keypair (`ssh-keygen -t ed25519` works) and register the public key
in the dashboard — OpenSSH `.pub` lines, PEM blocks, and raw base64 are all
accepted. Registering shows the **key id** you send with each request.

Send three headers:

- `x-stomped-key` — your key id.
- `x-stomped-timestamp` — unix milliseconds, as a string. Must be within
  **5 minutes** of stomped's clock.
- `x-stomped-signature` — `base64(ed25519_sign(message))` where the message is:

```
{timestamp}.{METHOD}.{path}.{body}
```

`METHOD` is uppercase, `path` includes the query string (e.g.
`/bots?mode=live`), and `body` is the exact raw bytes sent — empty string
for bodyless requests. The signature is bound to method, path, body, and a
timestamp the api accepts within ±5 minutes; there is no nonce, so a captured
request can be replayed as-is inside that window, but not altered or reused
elsewhere.

Complete client, using WebCrypto:

```js
async function stomped(privateKey, keyId, method, path, body) {
    const raw = body ? JSON.stringify(body) : "";
    const timestamp = String(Date.now());
    const message = `${timestamp}.${method}.${path}.${raw}`;
    const signature = Buffer.from(
        await crypto.subtle.sign("Ed25519", privateKey, new TextEncoder().encode(message)),
    ).toString("base64");
    return fetch(`https://api.stomped.dev${path}`, {
        method,
        headers: {
            "content-type": "application/json",
            "x-stomped-key": keyId,
            "x-stomped-timestamp": timestamp,
            "x-stomped-signature": signature,
        },
        body: raw || undefined,
    });
}

await stomped(privateKey, keyId, "POST", "/bots", {
    name: "my-first-bot",
    mode: "live",
    url: "https://your-bot.example.com",
});
```

Note this covers requests **to** stomped. Requests **from** stomped to your bot
(the play protocol) are always signed with the platform key — bearer keys
change nothing on that side.

## Bearer keys

Creating one shows the token — `stp_` followed by random base64 — **exactly
once**; stomped keeps only a hash. Send it on every request:

```
Authorization: Bearer stp_...
```

```js
await fetch("https://api.stomped.dev/bots", {
    method: "POST",
    headers: {
        "content-type": "application/json",
        authorization: `Bearer ${process.env.STOMPED_KEY}`,
    },
    body: JSON.stringify({
        name: "my-first-bot",
        mode: "live",
        url: "https://your-bot.example.com",
    }),
});
```

Treat the token like a password: env vars and secret stores, never the repo.
The `stp_` prefix exists so a leaked token is greppable. Lose it? Revoke the
key and create a new one — the token can't be shown again.

## Scopes

Every key carries **scopes**, fixed when the key is created; each endpoint in
this reference lists the scope it requires. A dashboard session (browser
cookie) carries every scope.

| scope | allows |
| --- | --- |
| `bots.read` | list and inspect your bots |
| `bots.write` | register, update, pause, remove bots |
| `keys.read` | list your API keys |
| `keys.write` | create and revoke API keys |
| `account.read` | read your account |
| `account.write` | change your handle |

Least privilege: a key for a bot-deploy script wants `bots.read` +
`bots.write` (the default) and nothing more — it then can't mint keys or
touch the account even if it leaks.
