Skip to content

Playground API

The playground API supports the Playground view of the SPA. All endpoints require an authenticated admin session and gateway-scoped access.


Issuing a playground token

POST /admin/v1/playground/token

Issues a short-lived gateway auth token for use by the Playground UI and by ordinary chat — both mint through this one route (the SPA's usePlayToken); there is no separate chat_enabled flag. The token is bound to the requested gateway, scoped to ["playground"], and expires 30 minutes after issuance (PLAYGROUND_TOKEN_TTL_S, raised from 10 minutes so a token that passes the app's pre-turn refresh check always outlives the longest legal turn). If a turn's request is nevertheless refused with 401 token_expired before any byte streamed (the token lapsed between that check and the request — a laptop that slept, a token rotated server-side), the chat app re-mints once through this route and replays the same turn; when the mint itself is refused because the browser session expired, it shows a typed "session expired" state in the conversation with a sign-in action rather than leaving the page mid-turn. See token_expired.

Not gated by playground_enabled. Unlike the playground-only search route (GET /admin/v1/playground/search), which returns 403 feature_disabled when the flag is off, this mint route is deliberately exempt from the tenant Playground kill-switch: gating it would break normal chat on a playground_enabled=0 tenant (403 feature_disabled). Minting requires only gateway-scoped access (require_gateway_access) plus the impersonation guard — never playground_enabled. The scope string ["playground"] is legacy/unrecognized, so the minted token is an unrestricted gateway inference credential (not a chat-only token). Consequently playground_enabled=0 is not a lever to stop a tenant's self-service inference — use budget limits, trial expiry, or deprovisioning for that.

Field Type Required
gateway_id string yes

The response is:

{
  "token": "myra_<HEX64>",
  "expires_at": "<ISO 8601>",
  "tenant_slug": "<TENANT>",
  "gateway_slug": "<GATEWAY>"
}

tenant_slug and gateway_slug are returned so the Playground UI can construct the inference URL. Calling the endpoint again deletes any expired playground tokens for the same gateway but leaves valid playground tokens in place; concurrent sessions are supported.


Searching the web (Playground)

GET /admin/v1/playground/search

Proxies a query to the gateway's configured web-search provider (web_search.provider: brave — US, default — or linkup — EU) on behalf of the gateway, using the same dispatch and error classification as live inference. The subscription key is read from the gateway's stored web_search.api_key configuration; if the gateway has no stored web_search.api_key, the response is 503 with an explanatory message. This endpoint deliberately reads the stored key, not the platform Linkup key that is injected at read time for every new production gateway (see inference): that shared platform key is spent only on the metered inference path, never through this unmetered admin search — so a gateway with no stored key correctly returns 503 here.

The gateway configuration is resolved with the tenant EU-residency floor applied, and the fail-closed EU gate runs first: if the gateway (or its tenant) enforces eu_region_routing and the configured provider is not EU-based (including the brave default), the response is 403 — the admin probe is never allowed to egress the query to a non-EU provider.

Required query parameters:

Parameter Type Description
q string Search query.
gateway_id string Gateway whose web_search.api_key is used.

The response is:

{
  "results": [
    { "title": "...", "url": "...", "snippet": "..." }
  ],
  "query": "<Q>"
}

Up to five organic results are returned.


Reading a playground trace

GET /admin/v1/playground/trace/<ID>

Returns the trace plus its ordered steps. The endpoint is also exposed at GET /admin/v1/traces/<ID>. Required role: tenant admin (or platform admin). The trace detail includes prompt and model-response content, so single-trace reads are restricted to tenant admins. A tenant admin must additionally have access to the trace's gateway; a gateway-scoped member or viewer gets 403. Platform admin can read any trace.

The response is:

{
  "trace": { "id": "...", "gateway_id": "...", "...": "..." },
  "steps": [
    { "name": "...", "started_at": ..., "ended_at": ..., "data": { ... } }
  ]
}

Each step's data field is JSON, decoded inline.