Embedded views

iframe a live, read-only, branded view of one run — no credential in the browser

What an embed is

An embed grant lets an external service (typically an LMS) drop a live view of one run — logs, metrics, streams, artifacts — inside its own page via an <iframe>, without ever handing a Симолёт credential to the browser. The embed token is the only thing the browser holds, and it is:

  • read-only — admits only a narrow whitelist of GET sub-resources of its one run;
  • single-job — bound to exactly one job id, checked in the auth guard and re-checked by the tenancy gate;
  • expiring — a signed expiry (default 24h, capped at 30 days); fail-closed once past;
  • allow-scoped — the grant may further narrow which categories (logs / metrics / streams / artifacts) are reachable; an empty set means all;
  • branded — an optional title / logo / accent for the stripped chrome (non-secret, content-addressed).

It’s a richer sibling of the Google-Docs-style share link (/s/{code}): where a share link is a durable URL you paste into chat, an embed is a live, service-driven, session-scoped view.

Mint a grant (service-only)

POST /v1/jobs/{id}/embed mints the grant. Minting is service-only: the caller must already be able to see the run and hold a runs management scope (runs:manage / runs:share / runs:submit, or *). A read-only share/embed view cannot mint further grants, and a viewer cannot mint. Requires the auth store (else 503).

curl -X POST $BASE/v1/jobs/<job_id>/embed \
  -H "Authorization: Bearer slt_…" -H "content-type: application/json" \
  -d '{
    "ttl_hours": 12,
    "allow": ["logs", "metrics"],
    "branding": { "title": "Lab 3 — Hover Task", "accent": "#5b8cff" }
  }'
# → {
#   "token": "…",
#   "url": "https://…/embed/e/<token>",   # ← iframe this
#   "expires_at": "2026-07-14T12:00:00Z"
# }

The body is optional — POST with no body mints a full-feature, unbranded grant at the default TTL. Then set the returned url as the iframe src; it 302-redirects to the stripped, branded SPA view carrying only the token and its one job id.

One-shot: mint at submit time

The LMS flow that launches a run and shows it immediately: add ?embed=1 to a run or job submit and the response carries the created run plus an embed grant — the branded live view is ready without a second round-trip.

curl -X POST "$BASE/v1/runs?embed=1" \
  -H "Authorization: Bearer slt_…" -H "content-type: application/json" \
  -d '{ "stack": "mujoco", "command": ["python", "main.py"] }'
# → { …job fields…, "embed": { "token": "…", "url": "https://…/embed/e/<token>", "expires_at": … } }

What the embed page reads

The stripped SPA view resolves the token to its grant with GET /v1/embed/grant — self-authenticating via the token (as the X-Embed-Token header or the ?embed= query param), no principal needed. It returns the job, the allow-set, the expiry, and the branding.

curl "$BASE/v1/embed/grant" -H "X-Embed-Token: <token>"
# → { "job": "job_…", "allow": ["logs","metrics"], "exp": 1760000000,
#     "branding": { "title": "Lab 3 — Hover Task", "accent": "#5b8cff" } }

From there the view reads only the whitelisted sub-resources of that one job (the run view, logs/events, recording, streams, artifacts) — each further gated by the grant’s allow-set. Any other path, any other job id, or any write fails closed. A missing / invalid / expired token is a 401.

Embeds are stateless with respect to revocation: rotate POLYGON_EMBED_SECRET (or the shared share secret) to invalidate every outstanding grant at once.