> ## 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.

<RequestExample>
  ```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],
  );
  ```
</RequestExample>

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

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](/api-sdk/realtime/realtime-sessions) 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.                                                                                              |
