- 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.
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 withSelect 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 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:
- Mint the replacement with the same scope set and a name that
identifies the new generation (for example
prod-submitter-2). - Deploy it to the workloads that hold the old token.
- Watch
Last usedon the old row. The keys table renderslast_used_atas a local timestamp, orneverif the key was never presented. Once the old key stops advancing, no traffic is still using it. - Revoke the old key.
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 kindcrypto_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 ano 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 withautoComplete="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 thebdnortpusurfaces. - 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.seterrors 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.
QUIC endpoint discovery
The QUIC endpoints table is populated bydevelopers.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.
Related
- Authentication — how API keys and short-lived tokens divide up
- Telemetry — the metric and trace streams that record these requests