API · Endpoints

HTML body

Read and write the body of an html-kind Surface. Three separate fields (html, css, js) stored independently, rendered together inside a sandboxed iframe served from render.trydock.ai. For block-level html inside doc bodies, see the Doc body API instead and emit a ```html fenced block.

Generated from src/lib/api-paths/html.ts. The body schema (UpdateHtmlSchema) is defined inline in that file.

get/api/workspaces/{slug}/html

Read an HTML surface body

Returns the resolved html surface's body (html, css, js). Empty fields when the surface exists but no body has been written yet. 404 when `?surface_slug=` doesn't match a live html surface.

Auth: Bearer token (API key or OAuth access token).

Path parameters

NameTypeRequiredDescription
slugstringyes

Query parameters

NameTypeRequiredDescription
surface_slugstringnoSurface slug for multi-html workspaces. Omit for the primary html surface (lowest position).
surfacestringnoLegacy alias for `surface_slug`. Same semantics. Prefer `surface_slug` in new code.

Responses

StatusBodyDescription
200objectResolved html body.
404Error`surface_slug` didn't match any live html surface in this workspace.

put/api/workspaces/{slug}/html

Replace fields on an HTML surface body

Partial-update friendly. Any of `html` / `css` / `js` can be omitted to leave the column unchanged; pass empty string to clear. HTML is sanitized at write time (see UpdateHtmlSchema). Emits `html.created` on first write to a surface, `html.updated` on subsequent writes — both carry per-field byte counts so webhooks can fan out a meaningful signal. Requires editor role.

Auth: Bearer token (API key or OAuth access token).

Path parameters

NameTypeRequiredDescription
slugstringyes

Request body

FieldTypeRequiredDescription
surface_slugstringnoOptional html surface slug. Omit to write to the primary html surface (lowest position).
htmlstringnoSanitized at write time. Caps: 50 KB per field. Omit to leave the column unchanged; pass empty string to clear. Sanitizer strips <script>, every on* event handler, javascript:/vbscript:/file:/data:text/html URIs, <iframe>, <object>, <embed>, <foreignobject>, <meta>, <base>, <link>, formaction smuggling. Allows inline <style>, ARIA attrs, form markup (rendered but inert), data:image/* URIs.
cssstringnoCSS source. Cap: 100 KB. Stored verbatim. Omit to leave unchanged; pass empty string to clear.
jsstringnoJS source. Cap: 100 KB. Stored but not executed at v1.

Responses

StatusBodyDescription
200objectWritten.
400ErrorBody shape failed validation, OR a field exceeded its byte cap (html 50KB, css 100KB, js 100KB, total envelope 250KB), OR sanitizer rejected the html field entirely.
404Error`surface_slug` didn't match any live html surface, or the workspace has no html surface yet.

Frequently asked questions

How do I read a Dock html surface body via the REST API?
GET `/api/workspaces/:slug/html`. Returns `{ html, css, js, updatedAt, updatedBy, updatedByPrincipalType, surfaceSlug }`. Empty strings when the surface exists but no body has been written yet. Multi-html workspaces accept `?surface_slug=<slug>` to target a specific tab.
How do I write to a Dock html surface body via the REST API?
PUT `/api/workspaces/:slug/html` with any combination of `{ html, css, js, surface_slug? }`. Omitted fields stay unchanged; pass empty string to clear. The html field is sanitized at write time. Editor role required.
What does the html sanitizer strip and what does it keep?
Stripped: <script>, every on* event handler (onclick, onload, etc.), javascript:/vbscript:/file:/data:text/html URIs, <iframe>, <object>, <embed>, <foreignobject>, <meta>, <base>, <link>, formaction/formmethod/formtarget smuggling. Allowed: standard HTML5 structural tags, inline <style>, ARIA attributes, form markup (rendered but inert at v1 since the sandbox is scripts-off), data:image/* URIs for inline images. See sanitizeHtmlBlock in src/lib/html-sanitize.ts.
What are the size caps on an html surface body?
Per-field: html 50 KB (post-sanitize), css 100 KB, js 100 KB. Total envelope: 250 KB across all three fields. Beyond these, split into multiple html surfaces or wait for the upcoming "site" workspace kind.
Does Dock execute the JS field?
Not at v1. The sandbox iframe runs with scripts disabled by default; the js column is stored for forward-compat. An interactive mode (workspace-setting toggle to flip `allow-scripts` on the sandbox) is queued as a follow-up.
How do I pre-flight an html payload before writing?
MCP `validate_html(html?, css?, js?)` returns `{ ok, errors, warnings, parsed }` with byte counts per field plus a `htmlDropped` warning when the sanitizer would strip anything from the html field. Pure analysis — never writes.
Can my AI agent get notified when a Dock html surface changes?
Yes. Subscribe to `html.created` for first-write signals or `html.updated` for subsequent edits. Both events carry per-field byte counts; `html.updated` also includes before/after deltas so consumers can render "Argus added 1.2 KB of CSS" style activity rows without diffing. Learn more →
Why is the rendered iframe served from a different origin?
Defense in depth. Even if a sanitizer slip leaked a <script> tag, executing it on render.trydock.ai (a different origin) means it can't read *.trydock.ai cookies, localStorage, Stripe state, or any other Dock surface. Browser cookie scoping by host enforces the isolation.
What's the difference between the ``` ```html ``` doc block and an html surface?
Block lives INSIDE a doc body, capped at 5 blocks * 50 KB per doc — for embedded mockups inside a brief or note. Surface IS its whole tab in a workspace, with three separately-stored fields, its own page, its own MCP tools — for mockups that are the artifact, not an illustration inside another artifact.