> ## Documentation Index
> Fetch the complete documentation index at: https://docs.qumo-deploy.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Signing Keys

> Register the Ed25519 key your app signs relay credentials with, and rotate or revoke it.

# Signing Keys

Your app decides who may publish and watch, and signs that decision as a short-lived
credential. It signs with an **Ed25519 signing key** that it generates and keeps; Qumo
stores only the public half. A relay admits a credential only when its key is registered
and every path it grants lies within the key's **prefix**.

<Warning>
  Managed relays start accepting credentials from registered keys in an upcoming release.
  Until then, registering a key has no effect on them.
</Warning>

Managed relays trust a key only while its project is a prod project and its workspace's
plan includes the managed relay network. If the workspace moves to a plan without it, its
keys stop being trusted within about a minute, which ends their sessions; they are trusted
again when the plan is restored, with nothing to register again. The same holds while
Qumo has suspended a project (`suspended_at` on the project): its keys stay registered
and are trusted again when it is resumed.

## The prefix

Every project has a root, `<workspace slug>/<project slug>`, for example
`acme/live-app`. The API returns both slugs (`slug` on the workspace and the project), and
they never change, even when you rename either. A key is confined to the project root, or
to a narrower path under it that you choose at registration (`live` gives
`acme/live-app/live`). A credential that grants a path outside the key's prefix is refused.

## Create a key

One step generates a key on your machine, registers its public half, and gives you the
private key. The private key is never sent to Qumo, and what you get is its only copy.

**With the CLI:**

```bash theme={null}
qumod keys create backend --project <project id> --prefix live --out signing-key.jwk
```

`--prefix` is optional and relative to the project root. `signing-key.jwk` is written
owner-only and is never overwritten.

**With the SDK:**

```ts theme={null}
import { QumoClient } from "@qumo-deploy/sdk";

const client = new QumoClient({ baseUrl: "https://api.qumo-deploy.com", token });
const { key, privateKey } = await client.signingKeys.create(projectId, {
  name: "backend",
  prefix: "live", // optional; relative to the project root
});
await Deno.writeTextFile("signing-key.jwk", JSON.stringify(privateKey), { mode: 0o600 });
console.log(key.kid, key.prefix); // the kid your credentials carry
```

Either way the private key is a JWK carrying its `kid` and `prefix`, the file
`qumo auth token` and the Go `token` package sign with. Keep it on your app's server.

## Register a key you already have

To generate the key yourself, use the `qumo` CLI. Pass the prefix the key will be
registered with, so `qumo auth token` and the `token` package refuse to sign outside it:

```bash theme={null}
qumo auth keygen -prefix acme/live-app/live -out signing-key.jwk
```

`signing-key.jwk` holds the private key. Keep it on your app's server.

Registration proves you hold the private key: you sign a dated message with it and send
the signature with the public key, in one request. The SDK does this, signing locally; the
private key is never sent:

```ts theme={null}
const privateKey = JSON.parse(await Deno.readTextFile("signing-key.jwk"));

const key = await client.signingKeys.register(projectId, {
  name: "backend",
  privateKey,
  prefix: "live", // optional; relative to the project root
});
console.log(key.kid, key.prefix); // the kid your credentials carry
```

Without the SDK, `POST /v1/projects/{id}/signing-keys` with
`{"name", "public_key": {"kty": "OKP", "crv": "Ed25519", "x"}, "prefix", "signed_at", "signature"}`:
`signed_at` is the current time in unix seconds, and `signature` is the Ed25519 signature,
base64url without padding, over the string
`qumo-signing-key-registration:<project id>:<signed_at>`. It is accepted within five
minutes of `signed_at`.

A project holds at most **five** active keys. A key is registered once across Qumo:
registering the same public key again, in any project, is refused.

## Rotation and revocation

A key is **active** until you **revoke** it
(`POST /v1/projects/{id}/signing-keys/{kid}/revoke`). Revoking is final: managed relays stop
trusting the key within about a minute, which ends the sessions it admitted, and the key
can't be registered again.

**To rotate without interrupting anyone:** create the new key, wait a minute, and switch
your app to sign with it. An hour later, once the last credential signed with the old key
has expired, revoke the old key.

**If a private key may be exposed,** revoke it at once. Its live sessions end; your clients
reconnect with credentials from another key.

## Permissions

Listing keys needs `signing_key.list`; registering and revoking need
`signing_key.manage`. For Identity Tokens, the scopes are `project:read` and
`signing_keys:admin`. See [Scopes & Permissions](/guides/scopes-permissions).


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.