Keyring

Embed "Manage API keys"

Drop one React component into your own dashboard and your customers manage their own keys, scoped to their own tenant.

@keyring/react renders a list of the signed-in customer's keys with create, rotate and revoke, and talks only to the tenant-scoped embed plane at /v1/embed/*. It holds only what your getToken() returns and refuses anything that looks like a krsk_ secret key before any network call.

1. Register your origin

Settings → Workspace settings → embed origins lists the origins your web app runs on, such as https://app.example.com, with no path. An embed token is bound to those origins and the embed plane honours a matching Origin header.

2. Mint a token on your server

Your server exchanges its secret key for a five-minute embed token scoped to one tenant. A Next.js Server Action is the shape below; any server-side function that can hold the secret key works.

export async function mintEmbedToken(): Promise<string> {
  const tenantId = await currentTenantId();
  const response = await fetch(
    `${process.env.KEYRING_BASE_URL}/v1/embed_tokens`,
    {
      method: 'POST',
      headers: {
        authorization: `Bearer ${process.env.KEYRING_SECRET_KEY}`,
        'content-type': 'application/json',
      },
      body: JSON.stringify({
        project_id: process.env.KEYRING_PROJECT_ID,
        env: 'live',
        tenant_id: tenantId,
        scopes: ['keys:read', 'keys:write', 'key_scope:orders:read'],
      }),
    },
  );
  if (!response.ok)
    throw new Error(`Could not mint an embed token: ${response.status}`);
  const body = (await response.json()) as { token: string };
  return body.token;
}

Scopes decide what the session may do: keys:read, keys:write, and key_scope:<name> for each scope the customer may put on a key they mint. Those decisions are made here, on your server, never in the browser.

3. Render the component

import { KeyringKeys } from '@keyring/react';
import { mintEmbedToken } from './actions';

export default function KeysPage() {
  return (
    <KeyringKeys
      getToken={mintEmbedToken}
      baseUrl={process.env.KEYRING_BASE_URL!}
    />
  );
}

baseUrl is required and has no default. The component never guesses a host.

Both files are compiled against the real package by the docs' test suite.

What it does

  • Calls getToken() on mount and again before the token expires, scheduled against a monotonic clock so a skewed browser clock cannot cause a refresh storm.
  • On any refused token, shows a Reconnect state that calls getToken() again, and never retries with the refused token.
  • Shows a minted or rotated key's plaintext once, and never re-displays a dismissed one; there is no control that can reopen it.
  • Renders only the controls the token's scopes grant. A keys:read token renders a read-only list.
  • Sends one request per click: mutations are single-flight.
  • Talks to nothing but the embed plane at baseUrl. No analytics, no third-party script, no iframe.

Markup

Unstyled, with a keyring-keys__* class on every element so you style it. There is no theming system in this version, and no web-component or iframe build.

Revoking a customer's sessions

Every embed token for a tenant carries the tenant's embed epoch. Bumping it from the tenant's page in the dashboard invalidates every outstanding token at once.

On this page