# Console (allocations and join codes)

> How the Arena console mints relay allocations and join codes, issues JWKS-signed host tokens, manages API keys, and how clients discover the QUIC endpoint.

The Arena console has two netcode screens that together cover the whole
join path: **Allocations** mints the join codes that peers redeem, and
**API keys** holds the API keys, the allocation API reference, and the
signing keys that sign allocation tokens. Both screens read live
FirstByte data — an org with no allocations and no keys shows empty
states rather than samples.

This page explains what the fields on those screens mean. For the Unity
transport swap, lanes, and the wire model, see the netcode details and
cookbook pages.

## What an allocation is

An allocation is a workspace-scoped record that binds one session
namespace to one relay endpoint, plus a **join code** that peers redeem
to reach it. The allocations table lists one row per allocation:

| Column      | Meaning                                                            |
| ----------- | ------------------------------------------------------------------ |
| Join code   | The shareable code peers redeem. Copyable from the row.            |
| Namespace   | The session namespace the allocation was minted for.               |
| Workspace   | The workspace (tenant) the allocation belongs to.                  |
| Region      | The edge the allocation was pinned to: `auto`, `fra`, or `sg`.     |
| Redeemed    | Peers redeemed so far, over the allocation's max players.          |
| Expires     | When the allocation stops being active.                            |
| State       | Allocation state chip (`active` while redeemable).                 |
| Tags        | Resource tags, stored against the `netcode_allocation` kind.       |

Rows are fetched by `fleetGames.netcodeAllocations.list({ orgId })`.
Tags are editable inline and are scoped to the same org.

## KPIs on the allocations screen

`fleetGames.netcodeAllocations.kpi({ orgId })` feeds the strip above the
table:

| KPI                | Source field          | What it counts                                   |
| ------------------ | --------------------- | ------------------------------------------------ |
| Active allocations | `active_allocations`  | Minted and not yet expired.                      |
| Peers redeemed     | `peers_redeemed`      | Join codes used.                                 |
| Token alg          | `token_alg`           | The algorithm of the org's active JWKS key. Shown as a warning when the org has no signing key. |
| Default TTL        | `default_ttl_hours`   | The default allocation lifetime, in hours, as configured server-side. |

The Token alg KPI is the quickest check for the tokenless case described
below: if it reads `—`, mints will return a join code but no signed
token.

## Mint a join code

The **Mint allocation** panel calls
`fleetGames.netcodeAllocations.mint({ orgId, namespace, maxPlayers, region })`.

| Field             | Rules                                                                 |
| ----------------- | --------------------------------------------------------------------- |
| Session namespace | Required. A session name, e.g. `season-3/match-118`. The platform scopes it to your organisation and returns `arena/<your-org-id>/<name>`, so you cannot name another tenant's namespace. |
| Max players       | Required integer, `1`–`1024`. The console's bounds mirror the mint procedure's own bounds. |
| Region            | `auto` (default edge), `fra` (Frankfurt), or `sg` (Singapore).         |

The response is `{ code, token, tokenless }`. On success the console
shows a one-time banner with the join code and, when the org has a
signing key, the host token:

- **Join code** — shown once in the banner, but also recoverable later
  from the allocations table row.
- **Host token** — shown **once only**. Copy it before dismissing the
  banner; the console does not re-display it, and there is no
  "show token" action on the table row.

The list and KPI queries are invalidated after a successful mint, so the
new row appears immediately.

## Host token issuance and signing keys

The host token is signed with the org's **active allocation signing
key**, published through the org's JWKS. Key material lives on the
API keys screen (`fleetGames.netcodeSigningKeys`), not on the
Allocations screen.

If the org has no signing key, minting still succeeds but the response
comes back with `tokenless: true` and a null token. The banner then says:

> Tokenless — create an allocation signing key in Developers to mint
> signed tokens.

To fix it, go to **API keys → Token discovery (JWKS)** and press
**Generate signing key** (`fleetGames.netcodeSigningKeys.create`, which
the console calls with `alg: 'Ed25519'`), then mint again. Existing
tokenless allocations are not retro-signed; their join codes remain
listed as-is.

## Regions and the single-relay rule

Every peer on an allocation shares **one** relay. The PoPs are not
meshed, so peers that land on different edges cannot see each other.
That is why region is chosen at mint time and then fixed for the life of
the allocation:

- `auto` — the default edge.
- `fra` — Frankfurt.
- `sg` — Singapore.

Pick the region closest to the session's players, and mint a separate
allocation per session rather than reusing one across regions.

## Redeem log and player caps

The **Redeem log** panel calls
`fleetGames.netcodeAllocations.redeemLog({ orgId, limit })` — the screen
requests the 20 most recent redemptions. Each entry shows:

| Column | Meaning                                            |
| ------ | -------------------------------------------------- |
| Code   | The join code that was redeemed.                   |
| Peer   | The peer id, truncated in the UI.                  |
| Reg    | The region the redeem resolved to.                 |
| Result | A tick for a successful redeem, a cross otherwise. |

Failed redeems appear here too, which makes this the first place to look
when a client reports that a code did not work. The per-row
`Redeemed / Max players` counter on the allocations table is the cap
side of the same story: the max players value you set at mint time is
what the counter is measured against.

## Expiry, revoke and state

An allocation leaves the active set when it expires (the `Expires`
column, driven by the default TTL shown in the KPI strip) or when you
revoke it.

**Revoke** calls `fleetGames.netcodeAllocations.revoke({ orgId, id })`.
The action is rendered only while the row's state is `active`; once the
state is anything else, the button is gone and the row cannot be
re-activated from the console. After a revoke the console invalidates
the list and KPI queries, so the row's state chip and the
**Active allocations** count update together.

Revoke is the only lifecycle control on this screen. There is no
extend-TTL action and no per-peer disconnect action in the console.

## Mint and revoke API keys

The API keys screen manages the bearer tokens used to call the
allocation API (`fleetGames.netcodeApiKeys`).

- **Create** — press **Create key**, give it a name (required, up to 80
  characters, e.g. `unity-client-prod`), and submit.
  `netcodeApiKeys.create({ orgId, name })` returns
  `token_cleartext`, which the console displays **once** in a banner:
  "copy your token now — it is shown only once". Afterwards the table
  shows only a `token_preview` prefix.
- **Revoke** — `netcodeApiKeys.revoke({ orgId, id })`. A revoked key
  keeps its row, dimmed, with a `revoked` chip in place of the Revoke
  button.

The table also shows the key's creation date and **Last used**, which is
blank (`—`) until the key has authenticated at least once — useful for
confirming which key a client build is actually presenting.

## Key scopes and environments

Each key row lists its **scopes** and its **environment**.

| Column | What it shows                                                                 |
| ------ | ----------------------------------------------------------------------------- |
| Scopes | The allocation-API capabilities the key carries — the console describes keys as scoped to *allocate* / *join*. |
| Env    | `live` keys are chipped in the success tone; non-`live` (test) keys in the secondary tone. |

Scopes and environment are properties of the key as returned by the
list procedure; the console does not edit them after creation. To change
either, create a new key and revoke the old one.

## The relay allocation API

The **Relay allocation API** panel is a static reference table for the
allocation endpoints, rendered from the screen's endpoint list. For each
endpoint it shows:

- the HTTP **method** (POST endpoints are chipped in the success tone,
  others in the secondary tone),
- the **path**, with a one-line note on what it does,
- the **request body** and, below the arrow, the **response** shape.

Authenticate these calls with an API key from the table above, presented
as a bearer token. Everything on this path rides QUIC/MoQT.

## Token discovery and signing-key rotation

The **Token discovery (JWKS)** panel shows the JWKS URL for the org and
the keys published at it (`fleetGames.netcodeSigningKeys.jwks({ orgId })`).
The URL is copyable — point your verifier at it rather than pinning a
key by value.

Each key entry shows:

| Field   | Meaning                                                        |
| ------- | -------------------------------------------------------------- |
| `kid`   | Key id. This is the `kid` header on host tokens signed with it. |
| `alg`   | Signing algorithm, with the curve appended when present.        |
| Rotated | When the key was last rotated.                                  |
| State   | Key state chip, so you can tell the active key from older ones. |

**Generate signing key** creates a new key for the org (the console
requests `Ed25519`) and invalidates the JWKS query, so the new `kid`
appears in the list. Verifiers that fetch the JWKS by URL pick up the new
key without a code change; because tokens carry `kid`, tokens signed by
an older key stay verifiable as long as that key is still published.

With no keys at all, the panel reads "No signing keys — add one to mint
signed allocation tokens", which is the same condition that makes mints
tokenless.

## Endpoint discovery over a DNS HTTPS record

Clients do not hardcode a port. They discover the QUIC port from a DNS
`HTTPS` resource record on `arena.firstbyte.fast`, which
advertises:

| Parameter | Value  |
| --------- | ------ |
| `alpn`    | `h3`   |
| `port`    | `4443` |

There is **no TCP fallback**. A client that cannot resolve the `HTTPS`
record, or that sits behind a network which blocks UDP, does not connect
at all — it does not silently downgrade. Treat "no HTTPS RR" and
"UDP blocked" as the two first hypotheses when a client never reaches the
relay and nothing shows up in the redeem log.

## Open a session from code

The API keys screen ships this snippet for a raw-QUIC session over the
C FFI core. The bearer is an API key from the table above; the namespace
is the same namespace you minted the allocation for.

```rust
// Rust — open a session and broadcast over the FFI core
use clutchcall::{Client, Role, Channel};

let net = Client::connect("arena.firstbyte.fast")
    .bearer(std::env::var("CLUTCHCALL_API_KEY")?)
    // the namespace the allocation returned — not one you invent
    .net_open(&allocation.session_namespace, Role::Server).await?;

net.on_peer(|peer, up| println!("peer {peer} {up}"));
net.send(/*peer=*/0, Channel::Reliable, &spawn_msg)?;   // 0 = broadcast
```

Notes on the shape of this API:

- `Role::Server` opens the session as the host. The host is the party
  that holds the host token from the mint banner.
- `on_peer` fires on peer join and leave — the `up` flag carries which.
- `send` takes a peer id and a channel; peer id `0` means broadcast to
  every peer on the allocation. `Channel::Reliable` selects the reliable
  lane.

## Related

- [Authentication](/concepts/authentication) — API keys, token minting, and key rotation across the platform
- [Telemetry](/platform/telemetry) — metrics and traces for the QUIC data plane
