The API keys screen in the Crypto console owns two credentials that every submit path depends on:
  • a scoped bearer token (API key) that the client presents on the QUIC surfaces, and
  • a per-tenant landing endpoint (your own Solana RPC, plus an optional Jito block-engine URL) that FirstByte uses to actually land your transaction.
Both are scoped to the organization selected in the console. The screen disables key minting and endpoint configuration when you are not signed in or when no organization resolves.

Scopes: bdn, tpu, feeds, read

A key carries a set of scopes, chosen at creation. The console offers exactly four, and at least one must be selected — otherwise the create panel refuses with Select at least one scope. Scopes are additive on a single key: a key with ["bdn","feeds"] can both submit through the BDN and subscribe to feeds. There is no scope editing after creation — to change the scope set, mint a replacement key and revoke the old one (see Rotating and revoking without dropping traffic). The scopes on each key are rendered in the Scope column of the API keys table, so you can audit which tokens hold submit rights without looking at the tokens themselves.

Minting a key

Click Create key. The panel asks for two things: On submit the console calls apiKeys.create({ orgId, name, scopes }). The name is trimmed before it is sent. Errors from the mutation are rendered inline next to the Create key button, so a rejected scope set or duplicate name shows up on the panel rather than as a toast. Pass the resulting token as a bearer credential on the QUIC surfaces:
The SDKs read the same value:

The one-time cleartext reveal

apiKeys.create returns token_cleartext exactly once, in the mutation response. The console renders it in a highlighted banner immediately after minting, with a copy button, and the banner is the only place the full token ever appears. Dismissing the banner discards it from the client — there is no procedure that reads it back. After that, the keys table shows only token_preview followed by a mask. Use the preview to correlate a row with a deployed secret; use the name and tags to correlate it with a workload. If you lose a token before storing it, the recovery path is to mint a replacement and revoke the lost one.

Rotating and revoking without dropping traffic

Revoking is a single call, apiKeys.revoke({ orgId, id }), wired to the trash icon on each row. Revoked keys are not deleted: the row stays in the table, dimmed, with the action cell replaced by the text revoked. That history is deliberate — it lets you see that a compromised key was retired rather than silently removed. Because revocation is immediate and there is no grace period, rotate in this order:
  1. Mint the replacement with the same scope set and a name that identifies the new generation (for example prod-submitter-2).
  2. Deploy it to the workloads that hold the old token.
  3. Watch Last used on the old row. The keys table renders last_used_at as a local timestamp, or never if the key was never presented. Once the old key stops advancing, no traffic is still using it.
  4. Revoke the old key.
Step 3 is the whole reason last_used_at is on the table. Revoking before it goes stale will cut off whatever is still presenting that token.

Tagging and filtering keys

Keys are taggable resources of kind crypto_api_key. Each row has a tag cell, and the card header carries a tag filter that reports how many of the total keys match the current filter. Tags are scoped to the same organization as the keys. This is the practical way to answer “which keys does the trading service hold” once you have more rows than fit on a screen, since the tokens themselves are masked.

BYO-RPC: why every tenant lands through its own endpoint

FirstByte does not land your transactions through a shared RPC pool. Every tenant submits through its own endpoint, and the endpoint must exist before you can submit. The Your RPC endpoints · BYO-RPC card is where you register it. Validation is strict about the scheme: both fields reject anything that is not a valid https URL, and the form refuses to save with Enter at least one endpoint URL if both fields are empty. Saving calls bdnRpc.set({ orgId, rpcUrl?, jitoUrl? }). Only the fields you actually typed are included in the payload — the console omits a blank field, and the placeholder says so (•••••••• (leave blank to keep)). So you can update the Jito URL without re-entering the RPC URL, and vice versa.

Adding a Jito block-engine URL for bundles

The Jito field is the bundle path. If you never submit bundles, leave it unset; the card will show a no Jito pill and that is a valid steady state. If you do submit bundles, set it to your block-engine bundles endpoint, for example a URL of the shape https://<region>.block-engine.jito.wtf/api/v1/bundles. The two endpoints are tracked independently — bdnRpc.get returns hasRpc and hasJito as separate booleans, and the card renders a separate pill for each. Having an RPC endpoint set does not imply bundle capability.

How sealed endpoints are stored and what is shown back

Endpoint URLs are sealed at rest and never shown back. Both inputs are password fields with autoComplete="off", so the browser does not retain them either. What bdnRpc.get returns is deliberately narrow: The host is echoed and nothing else. That is enough to confirm which provider you are landing through without exposing the API key that is embedded in the query string. If you need to verify the full URL, you cannot read it back — re-save it. Removing endpoints. The Remove all button calls bdnRpc.clear({ orgId, which: "all" }) and drops both the RPC and the Jito URL. The button only appears when at least one of them is set. After clearing, both pills revert to their no … state.

When no endpoint is configured

The BYO-RPC card is the gate on submitting, so the failure modes cluster around it:
  • No RPC endpoint set. The card shows no RPC. There is no shared fallback endpoint to land through — configure the RPC URL before your first submit on the bdn or tpu surfaces.
  • No Jito URL set. The card shows no Jito. Bundle submission has no block engine to reach. Non-bundle submits are unaffected.
  • Not signed in, or no organization. The card replaces both fields with a Sign in to configure state, because endpoint configuration is scoped to an organization. The Create key button is disabled for the same reason, with the tooltip naming which of the two conditions failed.
  • A rejected save. bdnRpc.set errors are rendered inline beside the Save endpoints button, using the message the procedure returned.
  • A non-HTTPS URL. Caught client-side before any call is made; the offending field is marked invalid.
A key with the right scope and no configured endpoint is not enough to land a transaction. Both halves of this screen have to be in place.

QUIC endpoint discovery

The QUIC endpoints table is populated by developers.endpoints and lists, for each surface, the HTTP method, the path, and the QUIC authority to connect to, plus a short note on what the surface does. Everything reaches clients over QUIC. There is no gRPC and no WebSocket client surface, and no fallback to TCP — a connection that cannot negotiate QUIC is refused. Clients discover the QUIC port from a DNS HTTPS resource record on the crypto authority, which advertises alpn=h3 and the port. The advertised ALPN set covers h3 and solana-tpu.
  • Authentication — how API keys and short-lived tokens divide up
  • Telemetry — the metric and trace streams that record these requests