---
title: "Api Sandbox App"
method: GET
path: "/api/apps/sandbox/{token}"
tags: ["apps"]
---

# Api Sandbox App

`GET /api/apps/sandbox/{token}`

## Request

- Base URL: `https://app.getdot.ai` (the document declares no server; this is the origin it was published from)
- URL: `https://app.getdot.ai/api/apps/sandbox/{token}`
- Auth: one of:
  - API key in header `X-API-KEY`
  - API key in cookie `Authorization`
  - OAuth 2 (password, token URL `/api/auth/token`)

Credential-free isolation envelope for a public app (ENG-1143).

Serves author content inside `<iframe sandbox="allow-scripts allow-forms">`
under a strict, immutable `default-src 'none'` CSP, so app JS runs in an
opaque origin that can neither read the viewer's Dot session/cookies nor
exfiltrate cross-origin. Gated by the org's Public-links setting exactly like
`/apps/public/{token}` (`_resolve_public_app` raises 401 for a restricted
org viewed anonymously), and it inherits `_public_cache_control` so a
member-gated render never lands in a shared cache.

Who may frame it depends on what it can reach: a member-gated wrapper is
restricted to our own origins, because the broker calls the API with the
viewer's session and author JS could postMessage the answer to a hostile
ancestor. A public wrapper stays open — third-party oEmbed embedding is the
point — and instead loses per-viewer state brokering when framed cross-site.

The frame is handed no token of its own: this document brokers its API
calls, authorizing each against `token` here — which the frame cannot read.
Views are counted when the broker fetches `/apps/public/{token}`, so
counting here too would double every open.

## Path parameters

- `token` string, required

## Response `200`

Successful Response

- unknown

## Other responses

- `422`: Validation Error

---

[API](https://skmtc.dev/getdot/apis/dot-api-2.md) · [All operations](https://skmtc.dev/getdot/apis/dot-api-2/llms.txt) · [OpenAPI document](https://skmtc.dev/getdot/apis/dot-api-2/revisions/d26fe7163a7f?raw)
