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

# Ephemeral Keys

> Embed GoldRush in client-side apps and WebSockets without shipping your API key. Ephemeral Keys are short-lived, refreshable child credentials minted by your API key, scoped to it, and safe to leak.

An **Ephemeral Key** is a short-lived credential that your backend mints from a normal GoldRush **API key** (`cqt_…` / `ckey_…`) and hands to an untrusted client - a browser, a mobile app, or a WebSocket URL. It is prefixed **`gr_ek_…`**, expires in **30 minutes** by default, is scoped to (and never wider than) the API key that minted it, and rolls all usage and billing up into that parent API key.

Ephemeral Keys exist so you can put GoldRush data directly in front of your end users **without ever exposing your API key to them**. A leaked Ephemeral Key is worthless within one refresh window.

<Info>
  **Two credentials, two jobs.** Your **API key** is a long-lived server-side secret - it mints Ephemeral Keys and is never shipped to a client. An **Ephemeral Key** is the disposable, client-side token your app actually connects with. This mirrors the "secret key mints an ephemeral key" pattern you may know from Stripe.
</Info>

## When to use Ephemeral Keys

Reach for Ephemeral Keys when the credential will live somewhere you don't control:

* **Client-side apps** - a browser SPA or mobile app that calls GoldRush directly, where any embedded API key is trivially extractable from network traffic or bundled JS.
* **WebSockets** - the Hyperliquid WebSocket (`wss://hypercore.goldrushdata.com/ws`) takes its credential as a `key` query parameter at connect time. That URL is visible to the client, so it must not carry your API key.
* **High-fan-out embeds** - thousands of end users each holding a credential. With Ephemeral Keys, each client gets its own short-lived token that self-expires within one refresh window.

If your GoldRush calls only ever happen **server-side**, you don't need Ephemeral Keys - just use your API key directly, as described in [Authentication](/docs/goldrush-foundational-api/authentication).

<Note>
  **Why not web-locked (referrer/origin) keys?** Origin allowlisting can gate browser REST traffic, but it does **not** cover WebSockets (there is no reliable `Origin` enforcement on a raw WS URL), and an origin restriction still ships your real API key to the client, where it can be lifted and replayed from any environment that spoofs the header. Ephemeral Keys solve both: the API key never leaves your backend, and the client-side token is worthless after 30 minutes.
</Note>

## The security model

The design rests on three properties:

1. **Your API key never reaches the client.** Only your backend holds it. It is used once per refresh, server-to-server, to mint an Ephemeral Key.
2. **Ephemeral Keys inherit the parent's access.** An Ephemeral Key carries exactly the parent API key's bindings - never wider, and in this release never narrower. The edge enforces the parent's access on every request, and all usage attributes to the parent, so quotas and billing are unchanged. (Per-child scope narrowing is planned but not part of this release.)
3. **Ephemeral Keys are safe if stolen.** They are signed **RS256 JWTs** with a 30-minute expiry. An attacker who captures one from a browser or a WS URL can use it only until it expires - a small, self-healing blast radius. No secret is recoverable from the token.

Because the token is a self-contained signed JWT, the GoldRush edge verifies it **offline** against a published public key (JWKS) - no database or cache round-trip per request. That is what makes Ephemeral Keys viable at Hyperliquid WebSocket message volume.

### The 30-minute TTL

The default TTL is **30 minutes** (1800 seconds); the maximum you can request is **60 minutes** (3600 seconds). Short TTLs are the primary revocation mechanism: instead of maintaining a per-token online blocklist, we let tokens expire quickly and refresh them on a timer.

* **Too short** and clients refresh constantly, adding mint load.
* **Too long** and a stolen token stays useful.
* **30 minutes** keeps the leaked-token window small while a client only mints \~48 times a day.

## Lifecycle: mint → embed → refresh

```
                                         ╔═ GoldRush control plane ═════════╗
╔══════════════════╗                     ║                                  ║░
║ Your backend     ║░                    ║   authenticate cqt_… · mint a    ║░
║ (holds API key)  ║░ ──① POST mint ───▶ ║   30-min RS256 JWT (gr_ek_…)     ║░
╚════════╤═════════╝░                    ║                                  ║░
 ░░░░░░░░│░░░░░░░░░░░                    ╚══════════════════════════════════╝░
                                          ░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░
         │ ② hand gr_ek_… to the client
         ▼
                                         ╔═ GoldRush edge ══════════════════╗
╔══════════════════╗                     ║                                  ║░
║ Client / browser ║░                    ║   offline-verify the JWT against ║░
║ (untrusted)      ║░ ──③ key=gr_ek_… ─▶ ║   JWKS · no mint round-trip      ║░
╚════════╤═════════╝░                    ║                                  ║░
 ░░░░░░░░│░░░░░░░░░░░                    ╚══════════════════════════════════╝░
                                          ░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░
         │ ④ ~5 min before exp: mint a fresh key and swap it in
         ▼
       (refresh loop repeats)
```

1. **Mint (server-side).** Your backend calls `POST /platform/ephemeral_keys/` with your API key and gets back `{ ephemeral_key, expires_at }`.
2. **Embed (client-side).** Your backend passes the `gr_ek_…` token to the client, which uses it exactly where it would have used an API key - as a `key` query parameter, a bearer token, or Basic Auth.
3. **Refresh (client-side).** Shortly before `expires_at`, the client asks your backend for a fresh Ephemeral Key and swaps it in. For long-lived WebSocket connections, reconnect (or re-authenticate) with the new token before the old one expires.

The [TypeScript SDK's `EphemeralKeyProvider`](#recipe-b-browser-websocket-with-auto-refresh) handles this refresh loop for you.

***

## Quick start (curl)

Two calls: your backend **mints** an Ephemeral Key with your API key, then the client **uses** that `gr_ek_…` token exactly where it would have used an API key.

**1. Mint (server-side, with your API key).**

```bash theme={null}
curl -X POST "https://api.covalenthq.com/platform/ephemeral_keys/" \
  -H "Authorization: Bearer $GOLDRUSH_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"ttl_seconds": 1800}'
# → { "ephemeral_key": "gr_ek_eyJhbGciOiJSUzI1NiIsImtpZCI6…", "expires_at": "2026-08-03T18:30:00+00:00" }
```

**2. Use (client-side, with the `gr_ek_…` token).** An Ephemeral Key authenticates the data APIs the same way an API key does - as a Bearer token, Basic Auth username, or `key` query parameter. Against the [Foundational API](/docs/goldrush-foundational-api/authentication):

```bash theme={null}
# Bearer token
curl -X GET "https://api.covalenthq.com/v1/eth-mainnet/address/demo.eth/balances_v2/" \
  -H "Authorization: Bearer $GOLDRUSH_EPHEMERAL_KEY"

# or as a query parameter (handy for URLs the client already holds)
curl -X GET "https://api.covalenthq.com/v1/eth-mainnet/address/demo.eth/balances_v2/?key=$GOLDRUSH_EPHEMERAL_KEY"
```

For the Hyperliquid WebSocket, pass it as the `key` query parameter at connect time:

```bash theme={null}
wscat -c "wss://hypercore.goldrushdata.com/ws?key=$GOLDRUSH_EPHEMERAL_KEY"
```

That is the whole flow. In production the client never sees your API key - it only ever receives the short-lived `gr_ek_…` token from your mint proxy ([Recipe A](#recipe-a-node-express-mint-proxy)) and refreshes it before `expires_at`.

***

## API reference

All Ephemeral Key management endpoints live under the Platform control plane at `https://api.covalenthq.com` and are authenticated **with your API key**, not with an Ephemeral Key. Each has a dedicated reference page under [API Reference › Ephemeral Keys](/docs/api-reference/ephemeral-keys/mint-an-ephemeral-key); the summary below is the narrative version.

### Mint an Ephemeral Key

```
POST /platform/ephemeral_keys/
```

Authenticate with a parent API key (`cqt_…` / `ckey_…`) as a bearer token. The request body is optional JSON. **An Ephemeral Key cannot mint another Ephemeral Key** - the request is rejected with `403` if authenticated with a `gr_ek_…` token (and likewise for a `gr_sk_…` service key).

<ParamField body="ttl_seconds" type="integer">
  Requested lifetime in seconds. Optional; defaults to **1800** (30 min). Clamped to `[1, 3600]` - the server maximum is **3600** (60 min), so larger requests are silently reduced to the cap. A non-integer value returns `400`.
</ParamField>

In this release the mint accepts no other fields - an Ephemeral Key always inherits the parent's full access (see [the security model](#the-security-model)). Any additional body fields are ignored.

**Response** `200 OK`

```json theme={null}
{
  "ephemeral_key": "gr_ek_eyJhbGciOiJSUzI1NiIsImtpZCI6…",
  "expires_at": "2026-08-03T18:30:00+00:00"
}
```

<ParamField body="ephemeral_key" type="string">
  The signed RS256 JWT, prefixed `gr_ek_`. This is what you hand to the client.
</ParamField>

<ParamField body="expires_at" type="string">
  ISO-8601 UTC timestamp at which the token's `exp` claim falls due. Refresh before this time.
</ParamField>

<Warning>
  This endpoint is **rate-limited per parent API key** (default **60 mints/minute**). Mint one Ephemeral Key per client session and refresh on a timer - do **not** mint a fresh token on every request. If you hit the limit you'll receive `429 Too Many Requests`.
</Warning>

### Ephemeral JWKS endpoint

```
GET /platform/.well-known/ephemeral-jwks.json
```

Public - no authentication. Returns the JSON Web Key Set of **public** keys used to sign Ephemeral Keys, so that any verifier (including the GoldRush edge) can validate a token's signature offline. Keys are identified by `kid`; up to two are active at once to allow zero-downtime rotation. Cache the response and refetch when you encounter an unknown `kid`.

```json theme={null}
{
  "keys": [
    { "kty": "RSA", "use": "sig", "alg": "RS256", "kid": "ek-2026-08a", "n": "…", "e": "AQAB" }
  ]
}
```

<Note>
  This is a **dedicated** key set for Ephemeral Keys. It is unrelated to any other GoldRush signing key.
</Note>

### Token claims

An Ephemeral Key is a standard RS256 JWT. Decode (without needing the private key) to inspect its claims - useful for debugging expiry:

| Claim           | Meaning                                                                                                                                                       |
| --------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `iss`           | Always `goldrush`.                                                                                                                                            |
| `sub`           | Unique id of this Ephemeral Key (a per-mint session id, e.g. `ek_…`).                                                                                         |
| `parent_key_id` | The minting API key's **non-secret public id** (a UUID) - usage, quota, and billing attribute here. The raw `cqt_`/`ckey_` secret never appears in the token. |
| `iat`           | Issued-at (UNIX seconds).                                                                                                                                     |
| `exp`           | Expiry (UNIX seconds). Verifiers reject the token after this.                                                                                                 |

The signing key id (`kid`) travels in the **JWT header** (not the claims), so a verifier can select the right JWKS key before checking the signature. There is no `scopes` claim in this release - a token inherits the parent's full access.

The token deliberately carries **no secret** - everything in it is safe for the client to see. It is trusted only because of the signature.

### Error codes

**Minting (the `platform/ephemeral_keys/` endpoint).** Errors are returned in the standard GoldRush envelope (`{ "error": true, "error_message": …, "error_code": … }`):

| Status                  | Cause                                                                                                                                          |
| ----------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------- |
| `400 Bad Request`       | Body isn't valid JSON, or `ttl_seconds` isn't an integer.                                                                                      |
| `401 Unauthorized`      | Missing/malformed `Authorization` header, or the bearer isn't a valid parent API key.                                                          |
| `403 Forbidden`         | The bearer is a `gr_ek_…` Ephemeral Key or a `gr_sk_…` service key (neither may mint), or the parent API key isn't enabled for Ephemeral Keys. |
| `404 Not Found`         | Ephemeral Keys aren't enabled on this deployment.                                                                                              |
| `429 Too Many Requests` | The parent API key exceeded its mint rate (default 60/min). Back off and retry.                                                                |

**Using an Ephemeral Key on the data APIs** (Foundational, JSON-RPC, Hyperliquid, x402):

| Status                         | Cause                                                | What the client should do                                                                      |
| ------------------------------ | ---------------------------------------------------- | ---------------------------------------------------------------------------------------------- |
| `401 Unauthorized` - *expired* | The `exp` claim has passed.                          | **Refresh:** mint a new Ephemeral Key from your backend and retry / reconnect.                 |
| `401 Unauthorized` - *invalid* | Signature failed, token malformed, or unknown `kid`. | Do **not** blindly retry - check that your backend returned a real `gr_ek_…` token unmodified. |
| `402 Payment Required`         | The parent API key has exhausted its credits.        | Billing issue on the parent - refreshing won't help.                                           |
| `429 Too Many Requests`        | Rate-limited on the parent's data rate class.        | Back off and retry with jitter.                                                                |

The distinct **expired** vs **invalid** `401` responses let SDKs refresh automatically on expiry without retry-looping on a genuinely bad token. The [`EphemeralKeyProvider`](#recipe-b-browser-websocket-with-auto-refresh) refreshes proactively, before `exp`, so a well-behaved client rarely sees an expired `401` at all.

***

## Recipes

### Recipe A - Node/Express mint proxy

Your backend exposes one small endpoint that mints an Ephemeral Key from your (secret) API key. This is the only place your API key is ever used.

The browser **does** call this proxy - that is the intended design, and it is safe for two independent reasons:

1. **The proxy is not open.** It sits behind your app's existing user session (cookie, session token, or whatever you already use to know who is logged in). A random visitor with no session gets `401` and never mints anything. Only your authenticated users can obtain a token, so nobody can run up your bill by hammering the endpoint. That is the job of `requireYourUserAuth` below - your own auth middleware, not something GoldRush provides.
2. **The token it returns is safe to hold in the browser.** An Ephemeral Key is *meant* to live in an untrusted client - it is a 30-minute, parent-scoped, secret-free JWT (see [the security model](#the-security-model)). What must never reach the browser is your **API key**; the Ephemeral Key reaching the browser is the whole point.

<Note>
  **Why isn't the mint endpoint itself the weak link?** A common worry: "if the browser can call `/api/goldrush-ephemeral-key`, can't anyone call it and steal a token?" No - because the endpoint is gated by *your* login (property 1), an unauthenticated caller gets nothing, and even a token handed to a legitimate-but-untrusted client is disposable by design (property 2). The endpoint returns a short-lived child credential to people who are *already* your logged-in users; it never returns your API key.
</Note>

```javascript theme={null}
// server.js - never ships to the client
import express from "express";

const app = express();
const GOLDRUSH_API_KEY = process.env.GOLDRUSH_API_KEY; // cqt_… - server-side secret

// requireYourUserAuth is YOUR existing session check - e.g. verify the session
// cookie / auth token on req and reject with 401 if the caller isn't logged in.
// GoldRush does not provide this; it's the same auth that guards your other
// authenticated routes.
app.post("/api/goldrush-ephemeral-key", requireYourUserAuth, async (req, res) => {
  const r = await fetch("https://api.covalenthq.com/platform/ephemeral_keys/", {
    method: "POST",
    headers: {
      Authorization: `Bearer ${GOLDRUSH_API_KEY}`,
      "Content-Type": "application/json",
    },
    // Only ttl_seconds is accepted today; the token inherits the API key's access.
    body: JSON.stringify({ ttl_seconds: 1800 }),
  });

  if (!r.ok) {
    return res.status(502).json({ error: "mint_failed", status: r.status });
  }
  const { ephemeral_key, expires_at } = await r.json();
  // Hand only the short-lived token to the client - never the API key.
  res.json({ ephemeral_key, expires_at });
});

app.listen(3000);
```

<Warning>
  Guard this route with **your own** authentication (`requireYourUserAuth` above). An open mint proxy lets anyone mint tokens against your API key and your bill.
</Warning>

### Recipe B - Browser WebSocket with auto-refresh

Connect a browser to the Hyperliquid WebSocket using an Ephemeral Key, and refresh it before it expires. The [TypeScript SDK](/docs/goldrush-sdks)'s `EphemeralKeyProvider` calls your mint proxy (Recipe A) and keeps a valid token ready:

```typescript theme={null}
import { EphemeralKeyProvider } from "@covalenthq/client-sdk";

// Points at YOUR backend proxy, not directly at GoldRush.
const keys = new EphemeralKeyProvider({
  mintUrl: "/api/goldrush-ephemeral-key",
  refreshSkewSeconds: 300, // refresh ~5 min before exp
});

async function connect() {
  const token = await keys.getToken(); // mints on first call, cached after
  const ws = new WebSocket(`wss://hypercore.goldrushdata.com/ws?key=${token}`);

  ws.onopen = () => {
    ws.send(JSON.stringify({ method: "subscribe", subscription: { type: "l2Book", coin: "BTC" } }));
  };
  ws.onmessage = (ev) => handleBookUpdate(JSON.parse(ev.data));

  // A stale/expired token closes the socket with a 401-class reason: reconnect with a fresh one.
  ws.onclose = async () => {
    await keys.refresh();      // force a new token
    setTimeout(connect, 1000); // reconnect with jittered backoff in production
  };
}

// Proactively reconnect before the current token expires, so the socket never
// drops mid-stream. Ephemeral Keys can't hold a connection open past their exp.
keys.onBeforeExpiry(() => connect());

connect();
```

If you prefer not to use the SDK, the manual pattern is the same: fetch a token from your proxy, open `wss://hypercore.goldrushdata.com/ws?key=<gr_ek_…>`, and set a timer for `expires_at − 5 min` that fetches a fresh token and reconnects. See the [WebSocket API overview](/docs/goldrush-hyperliquid/websocket-api/overview) for subscription payloads.

<Note>
  A WebSocket authenticated with an Ephemeral Key **cannot outlive the token's expiry** beyond the max TTL - the edge drops or requires re-auth at `exp`. Always reconnect with a fresh token before then; don't rely on an open socket staying open indefinitely.
</Note>

***

## FAQ

* **Do Ephemeral Keys change my billing?**
  No. All usage attributes to the parent API key's `parent_key_id`, exactly as if the parent had made the calls. There is no separate meter or plan for Ephemeral Keys.

* **Can I mint an Ephemeral Key from another Ephemeral Key?**
  No. Only a parent API key (`cqt_…` / `ckey_…`) can mint. Minting with a `gr_ek_…` token is rejected - this prevents a leaked client token from bootstrapping fresh long-lived access.

* **Can I restrict what an Ephemeral Key can do (per-child scopes)?**
  Not in this release. A token inherits exactly the parent API key's access - it can't be widened, and per-child narrowing (fewer chains/endpoints) is planned but not yet available. The only mint-time parameter today is `ttl_seconds`.

* **If the browser calls my mint proxy, can't anyone call it and steal a token?**
  No. Your mint proxy sits behind your app's own login (the `requireYourUserAuth` step in [Recipe A](#recipe-a-node-express-mint-proxy)) - an unauthenticated request gets `401` and mints nothing, so nobody can run up your bill. And a token handed to one of your legitimate users is safe to hold client-side anyway: it is a 30-minute, parent-scoped, secret-free JWT. The thing that must stay server-side is your **API key**, never the Ephemeral Key.

* **Is this the same as a ServiceKey?**
  No - different job. [ServiceKeys](/docs/service-keys) authenticate **account-level Platform operations** (like reading your usage) and are rejected on the data APIs. Ephemeral Keys authenticate **data** requests on behalf of a parent API key and are for client-side embedding. See also [Authentication](/docs/goldrush-foundational-api/authentication) for server-side API-key usage.
