# 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 **Ed25519 request signatures**:
you hold a private key, bojo holds only the public half. Generate a keypair
(`ssh-keygen -t ed25519` works) and register the public key in the dashboard
under **API keys** — OpenSSH `.pub` lines, PEM blocks, and raw base64 are all
accepted. Registering shows the **key id** you send with each request.

## Signing a request

Send three headers:

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

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

`METHOD` is uppercase, `path` includes the query string (e.g.
`/bots?game=pioneers`), and `body` is the exact raw bytes sent — empty string
for bodyless requests. The signature is bound to method, path, and time, so a
captured request can't be replayed elsewhere.

Complete client, using WebCrypto:

```js
async function bojo(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.pawnd.org${path}`, {
        method,
        headers: {
            "content-type": "application/json",
            "x-bojo-key": keyId,
            "x-bojo-timestamp": timestamp,
            "x-bojo-signature": signature,
        },
        body: raw || undefined,
    });
}

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

## 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` | register 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.
