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 returns403 feature_disabledwhen the flag is off, this mint route is deliberately exempt from the tenant Playground kill-switch: gating it would break normal chat on aplayground_enabled=0tenant (403 feature_disabled). Minting requires only gateway-scoped access (require_gateway_access) plus the impersonation guard — neverplayground_enabled. The scope string["playground"]is legacy/unrecognized, so the minted token is an unrestricted gateway inference credential (not a chat-only token). Consequentlyplayground_enabled=0is 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:
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.