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

# Client Secrets

> Mint a short-lived secret that lets a browser open a realtime session without an API key.

```bash cURL theme={null}
curl https://gateway.bud.studio/v1/realtime/client_secrets \
  -H "Authorization: Bearer YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "expires_after": { "anchor": "created_at", "seconds": 600 },
    "session": { "model": "my-realtime-deployment" }
  }'
```

```javascript Node (your server) theme={null}
const res = await fetch("https://gateway.bud.studio/v1/realtime/client_secrets", {
  method: "POST",
  headers: {
    Authorization: `Bearer ${process.env.BUD_API_KEY}`,
    "Content-Type": "application/json",
  },
  body: JSON.stringify({
    expires_after: { anchor: "created_at", seconds: 600 },
    session: { model: "my-realtime-deployment" },
  }),
});
const { value, expires_at } = await res.json();
// Send only `value` to the browser.
```

```javascript Browser theme={null}
// `ek` is the `value` your server returned.
const ws = new WebSocket(
  "wss://gateway.bud.studio/v1/realtime?model=my-realtime-deployment",
  ["realtime", "openai-insecure-api-key." + ek],
);
```

```json 200 theme={null}
{
  "value": "ek_bud_k1.Xq3v0l9...",
  "expires_at": 1790000600,
  "session": {
    "model": "my-realtime-deployment"
  }
}
```

A browser cannot set an `Authorization` header on a WebSocket, and an API key shipped to a browser
is an API key anyone can read. A client secret solves both: your server, which holds the API key,
mints a secret that lasts minutes and is tied to one deployment, and the browser connects to
[realtime sessions](https://docs.budecosystem.com/api-sdk/realtime/realtime-sessions.md) with it.

The request and response have the shape of OpenAI's `client_secrets` API, so code written to mint
an OpenAI ephemeral key needs only the gateway URL and your Bud credential.

## Headers

| Parameter | Type | Required | Description |
| - | - | - | - |
| Authorization | string | Yes | `Bearer` and a Bud API key or a Keycloak access token. A client secret cannot mint another client secret: an `ek_bud_` credential is refused with `401`. |
| Content-Type | string | Yes | `application/json` |

## Body

| Parameter | Type | Required | Description |
| - | - | - | - |
| expires\_after.anchor | string | No | `created_at`. |
| expires\_after.seconds | integer | No | How long the secret lasts, `10` to `7200`. Default: `600`. Outside that range: `400`. |
| session.model | string | Yes | The realtime deployment the secret is for. It must be a deployment your credential can use (otherwise `403`) and must serve realtime sessions (otherwise `404`). Missing: `400`. |
| session.\* | object | No | Other session settings are checked against the deployment's policy and returned in the response, but **not** bound to the secret: a browser holding it can still change them within the deployment's policy. |

## Response

| Field | Type | Description |
| - | - | - |
| value | string | The secret, `ek_bud_…`. Between 250 and 350 characters. |
| expires\_at | integer | When the secret stops opening sessions, in Unix seconds. |
| session | object | The session settings you sent, as checked. |

The secret is opaque and encrypted. Do not parse it: it carries no project, user or deployment ID
anyone holding it could read, and changing any character makes it invalid.

## How long a secret lasts

A secret never outlives the credential that minted it: `expires_at` is the earlier of the time you
asked for and the parent credential's expiry. When it is shortened, the response says so through
`expires_at` rather than failing.

* **Minted with a Keycloak access token.** The token's own expiry caps the secret. Keycloak access
  tokens usually last about 5 minutes, so a secret minted from one lasts at most that long. A token
  with fewer than 10 seconds left is refused with `401 credential_expiring`.
* **Minted with an API key.** The secret's own expiry applies, and the secret stops working as soon
  as its API key is deleted or expires, whichever comes first.

## Using a secret

Open the socket with the secret in the WebSocket subprotocol, and `model` naming the deployment the
secret was minted for:

```text theme={null}
wss://gateway.bud.studio/v1/realtime?model=my-realtime-deployment
Sec-WebSocket-Protocol: realtime, openai-insecure-api-key.ek_bud_...
```

When the socket opens, the gateway checks that:

1. the secret is intact and has not expired (otherwise `401`);
2. `model` names the deployment it was minted for (otherwise `403`);
3. the credential that minted it is still valid and can still use that deployment.

A secret can open any number of sessions until `expires_at`. A session that is already open keeps
running after the secret expires, but not after its parent credential is revoked: it is closed
with `session_revoked` within 30 seconds for an API key, or within 30 seconds plus the gateway's
authorization cache lifetime (5 minutes by default) for a Keycloak token.

Usage from a session opened with a secret is attributed and billed to the project, user and API key
that minted it.

## Errors

| Status | When |
| - | - |
| `400` | `seconds` outside `10`–`7200`, or no `session.model`. |
| `401` | No credential, an invalid one, an `ek_bud_` secret used to mint, or a Keycloak token with less than 10 seconds left (`credential_expiring`). |
| `403` | The credential cannot use `session.model`. |
| `404` | `session.model` does not exist or does not serve realtime sessions. |
| `501` | Client secrets are not enabled on this gateway. |


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.