Skip to content

Guides

Restricted keys and short-lived connection tokens

Two credentials, two jobs. A restricted key identifies your server to the API; a connection token admits one participant to one session for about an hour.

Video has a credential problem most integrations solve badly. The browser needs permission to join the call, but the browser is the one place you cannot keep a secret: anything shipped to it is public. So teams either mint self-signed tokens and hope the signing holds, or — worse — embed a server key in client code where anyone can extract it and run up the bill.

Synento splits the problem in two. Your server holds a restricted key: project-scoped, revocable in one call, and deliberately weak — it can create sessions, mint connection tokens and read manifests in its own project, and nothing else. Each participant gets a connection token: short-lived, scoped to one session and one user, carried as a WebSocket subprotocol so it never lands in URLs or logs.

This guide mints a restricted key, builds the token route every browser client needs, shows how rotation falls out of reconnects for free, and revokes the key when it is no longer needed.

1. Three credentials, three jobs

There are exactly three credentials in the system, and confusing them is the whole threat model. Owner keys start with sk_live_ and act for the organisation: they manage projects, keys and billing. Restricted keys start with rk_live_ and act for one project on the integration routes only. Agent and OAuth tokens act for an AI integration and are accepted only by the MCP server. The boundaries are enforced by which store each credential is looked up in, not by convention — a restricted key cannot list keys, mint inspect tokens, or touch another project, because the lookup for those routes never sees it.

  • sk_live_… — the organisation credential. Lives in your secrets manager, manages everything, never reaches a browser or a repo.
  • rk_live_… — the integration credential. One project, sessions plus tokens plus manifests. This is what your server runs on.
  • Connection tokens — the participant credential. One session, one user, about an hour. This is all the browser ever holds.

2. Mint a restricted key

Minting takes an owner credential — a dashboard session or an sk_live_ key — because a restricted key must never be able to mint its own siblings. The secret is returned exactly once: store it in the server environment that needs it and move on. Name it after the app it serves, so revocation later is a decision you can make without archaeology.

terminal
# Needs an owner credential: a dashboard session or an sk_live_ key.
# The rk_live_ secret below is shown exactly once.
curl -s -X POST https://api.synento.com/v1/restricted-keys \
  -H "Authorization: Bearer $SYNENTO_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"project_id": "prj_abc", "label": "standup app"}' | jq -r .data.secret

3. Build the one token route

Browsers join with a token your server mints per participant. The route below is the entire credential surface of most integrations: it takes a session and user id, mints a token scoped to both, and returns it with the WebSocket URL. The token carries no secret in the URL — the browser SDK sends it as a subprotocol — and it expires after about an hour, so a leaked token is a small, self-closing incident.

Run this route on the restricted key. Then a leaked server credential can only mint tokens in one project, never read another project's sessions, and never mint an inspect token — the operator view of a call stays an owner-only capability.

Watch what refusal looks like, because your logs will show it one day: a restricted key asking for inspect: true gets a FORBIDDEN response naming the restriction, and a restricted key wandering to another project's session gets the same. Those errors are the boundary working, not the integration failing — build support tooling on owner credentials and leave the integration key weak.

app/api/synento/route.ts
import { SynentoClient, resolveWsUrl } from '@synento/node'

// This route may run on the restricted key: it only creates sessions and
// mints tokens in its own project. It can never list keys or mint inspect
// tokens, so a leaked key stays a small incident.
const synento = new SynentoClient() // SYNENTO_API_KEY=rk_live_…

export async function POST(request: Request) {
  const { sessionId, userId } = await request.json()
  const token = await synento.createConnectionToken(sessionId, { userId })
  return Response.json({
    // expires_in is an hour by default; the browser SDK re-mints on every
    // reconnect, so expiry rotates tokens for free.
    expiresIn: token.expiresIn,
    token: token.accessToken,
    wsUrl: resolveWsUrl({
      apiBaseUrl: process.env.SYNENTO_API_URL!,
      requestOrigin: request.headers.get('origin'),
      sessionId,
      userId,
    }),
  })
}

4. Rotation falls out of reconnects

Tokens are short, and that is a feature, not a chore. The browser SDK calls your getToken provider on every connect and every reconnect, so each new connection arrives with a freshly minted token and expired ones simply stop working. There is no refresh dance to implement and no long-lived browser credential to steal: the steady state of the system is tokens constantly rotating underneath live calls.

The same logic applies one level up. Mint one restricted key per environment and per app, label them, and revoke the individual key when an app is retired or a secret leaks — never rotate the owner key in a panic, because that breaks everything at once instead of the one thing.

5. Revoke the moment it is unneeded

Revocation is one call and takes effect immediately for the key itself: no new sessions, tokens or manifest reads authenticate on it from that moment. Connection tokens already minted are self-contained and expire within the hour, so the tail closes on its own. Make this part of your offboarding checklist for apps, environments and contractors, the same way you would a database password.

terminal
curl -s -X DELETE https://api.synento.com/v1/restricted-keys/rk_abc \
  -H "Authorization: Bearer $SYNENTO_API_KEY" | jq .data.revoked

6. Name keys after their app and environment

The label you set at mint time is the only thing standing between an orderly rotation and archaeology. One key per app per environment — standup app (prod), research (staging) — so revoking one breaks exactly one thing. List the project's keys when someone leaves or an app retires, revoke what is stale, and mint fresh for what replaces it.

Key management itself always takes an owner credential, in the dashboard or over the API: restricted keys can use the integration routes but never the key routes, so a compromised app server cannot mint itself a sibling or unrevoke its replacement. That one-way door is what makes the restricted key safe to put in more places than you would ever put the owner key.

  • People manage keys in the dashboard; apps use them from the environment. Never invert that.
  • When in doubt between two credentials, ship the weaker one. You can widen access later; you cannot un-leak a key.

Common questions

A restricted key leaked into a log. How bad is it?

Bad, but bounded: rotate it with one call. While it was live it could only create sessions, mint tokens and read manifests in its own project — no key management, no other projects, no billing. That bound is the reason servers run on restricted keys instead of owner keys.

Can the browser call the API directly with the restricted key?

It physically can, and you must not let it. Ship only connection tokens to the browser. A key in client code is extractable by anyone, and even a weak key mints sessions that bill to you.

What is an inspect token?

An operator's view of a call rather than a participant's seat in it. Minting one needs an owner credential; restricted keys are refused. Keep it that way — support tooling should authenticate as your team, not as the integration.

What happens when a token expires mid-call?

Nothing, until the next reconnect: the open connection already authenticated. The reconnect mints a fresh token through getToken, so expiry rotates credentials without dropping calls.

Give your agent a video platform it can verify.

Sign up, point your agent at llms.txt, and have a recorded call running before lunch.