# 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 pointed at by `CLUTCHCALL_CREDENTIALS`; the
public half is registered with the gateway via the methods on this page.

> **WARNING:**
> No SDK wrapper. Hit these methods directly via the [raw RPC envelope](/rpc/envelope-format).

## `PublishServiceAccount`

Register a new public key for a tenant, or rotate in a new one alongside
an existing one.

| 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 — useful for
rolling rotations.

## `RevokeServiceAccount`

Tear down a key. The gateway stops accepting JWTs signed with the matching
`kid`; any in-flight QUIC connection authenticated with that `kid` is
closed 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 when you want to hand a token to a frontend (e.g. for the
TypeScript SDK in browser mode) without exposing the service-account
private key itself.

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`  | Hard-capped at 300 by the gateway.                   |
| `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
(`Origin` header sent during the WebTransport handshake). A token
issued for `tenant_a` cannot be redirected to `tenant_b`'s gateway URL.

## Rotation pattern

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

This keeps every active call running through the rollover.
