# Service Accounts

> Mint, list, and revoke the JWT signing keys your SDKs use.

A **service account** is an RSA keypair scoped to a tenant. The private
half lives in the JSON file that `CLUTCHCALL_CREDENTIALS` points at. You
register the public half with the gateway through the methods on this
page.

> **WARNING:**
> There is no SDK wrapper. Use the [raw RPC envelope](/rpc/envelope-format) to call these methods.

## `PublishServiceAccount`

Register a new public key for a tenant. For rotation, you can also add
a new key next to an existing key.

| Field             | Type     | Notes                                                    |
| ----------------- | -------- | -------------------------------------------------------- |
| `admin_token`     | `string` | Admin JWT.                                               |
| `tenant_id`       | `string` | Target tenant.                                           |
| `private_key_id`  | `string` | Stable `kid`; embedded in JWTs minted with the matching private key. |
| `public_key_pem`  | `string` | RSA public half in PEM (`-----BEGIN PUBLIC KEY-----`).   |
| `role`            | `string` | `"sdk"` or `"admin"`. `"admin"` accounts can call this API. |
| `expires_at_ms`   | `int64`  | Unix epoch ms; `0` = never.                              |

Returns `Empty`. Multiple keys can coexist for one tenant. This is
useful for rolling rotations.

## `RevokeServiceAccount`

Remove a key. The gateway stops accepting JWTs signed with the matching
`kid`. The gateway closes each in-flight QUIC connection authenticated
with that `kid` within ~1 RTT.

| Field             | Type     |
| ----------------- | -------- |
| `admin_token`     | `string` |
| `tenant_id`       | `string` |
| `private_key_id`  | `string` |

Returns `Empty`.

## `SessionAuth`

Mint a short-lived browser-scoped JWT from an existing service account.
Use this method to give a token to a frontend (e.g. the TypeScript SDK
in browser mode). The frontend never sees the service-account private
key.

Request (`SessionAuthRequest`):

| Field             | Type     | Notes                                                |
| ----------------- | -------- | ---------------------------------------------------- |
| `admin_token`     | `string` | Admin JWT.                                           |
| `tenant_id`       | `string` |                                                      |
| `subject`         | `string` | Free-form (typically your end-user's UUID).          |
| `ttl_seconds`     | `int32`  | The gateway hard-caps this at 300.                   |
| `scope`           | `string` | Space-separated; `"call.dial call.barge"` etc.       |

Response (`SessionAuthResponse`):

| Field           | Type     | Notes                                            |
| --------------- | -------- | ------------------------------------------------ |
| `token`         | `string` | The JWT. Pass it to the browser SDK as the second constructor arg with `isBrowserToken=true`. |
| `expires_at_ms` | `int64`  | Unix epoch ms.                                   |
| `error_message` | `string` | Empty on success.                                |

The gateway pins the issued token to the requesting tenant's origin
(the `Origin` header sent during the WebTransport handshake). You
cannot redirect a token issued for `tenant_a` to `tenant_b`'s gateway
URL.

## Rotation pattern

1. Call `PublishServiceAccount` with the new `private_key_id`. Both keys
   are now valid.
2. Update every host that runs the SDK to use the new private key.
3. Wait until all in-flight tokens signed with the old key expire
   (≤ 1 hour by default).
4. Call `RevokeServiceAccount` for the old `private_key_id`.

Every active call continues to run through the rollover.
