API · Endpoints

Surfaces

A workspace holds one or more surfaces (tabs). Each surface is either a typed-row table or a TipTap rich-text doc. These endpoints add, list, reorder, rewrite the column schema, and remove tabs.

Generated from src/lib/api-paths/surfaces.ts: what you see here matches what the runtime validates.

get/api/workspaces/{slug}/surfaces

List surfaces in a workspace

Returns every surface (tab) in the workspace, in tab order.

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

Path parameters

NameTypeRequiredDescription
slugstringyes

Responses

StatusBodyDescription
200objectSurface list.
403ErrorCaller can't access this workspace.

post/api/workspaces/{slug}/surfaces

Add a surface (tab)

Append a new surface to the workspace. Editor role required.

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

Path parameters

NameTypeRequiredDescription
slugstringyes

Request body

FieldTypeRequiredDescription
kind"table" | "doc"yesWhether this tab is a typed-row table or a TipTap rich-text doc.
namestringnoDisplay name. Auto-generated if omitted.
slugstringnoURL slug. Auto-derived from name if omitted.
columnsobject[]noOptional initial columns for `kind: 'table'`. Defaults to Title/Status/Notes when omitted. Ignored on doc surfaces.

Responses

StatusBodyDescription
200objectCreated.
403ErrorCaller lacks editor role.

patch/api/workspaces/{slug}/surfaces/{surfaceSlug}

Rename, reslug, reorder, OR replace a surface's column schema

Rename, change the URL slug, move a surface within its workspace's tab strip, OR replace the column schema (table surfaces only). Pass any subset of `name`, `slug`, `position`, `columns`. Position uses 0-based indexing and other tabs shift to keep positions contiguous. Column replace fires `surface.columns_updated`; row.data keys not in the new schema are preserved on disk. Editor role required. Slug renames preserve old URLs via the surface alias table.

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

Path parameters

NameTypeRequiredDescription
slugstringyes
surfaceSlugstringyes

Request body

FieldTypeRequiredDescription
namestringnoNew display name.
slugstringnoNew URL slug. Lowercase kebab-case, 3-64 chars. Must be unique within the workspace; old slug stays redirectable via the surface alias table.
positionintegernoOptional reorder. Equivalent to POST /surfaces/{slug}/move when only repositioning.
columnsobject[]noWholesale replacement of the table surface's column schema. Table surfaces only — doc/html surfaces 400 with a table-only error. Each ColumnDef: `{ key, label, type, position, width?, hidden?, description?, options? }`. `type` ∈ text | longtext | url | status | owner | date | number. `options` is required on status/owner. Existing row.data keys not in the new column set are preserved on disk (they just stop rendering until a column with that key is added back). Fires `surface.columns_updated`.

Responses

StatusBodyDescription
200objectUpdated.
403ErrorCaller lacks editor role.
409ErrorSlug collides with another surface in the workspace.

delete/api/workspaces/{slug}/surfaces/{surfaceSlug}

Delete a surface

Remove a tab. The workspace must keep at least one surface; deleting the last one returns 400.

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

Path parameters

NameTypeRequiredDescription
slugstringyes
surfaceSlugstringyes

Responses

StatusBodyDescription
200objectDeleted.
400ErrorWorkspace would have no surfaces left.

post/api/workspaces/{slug}/surfaces/{surfaceSlug}/move

Reorder a surface

Change a surface's tab position. Other tabs shift accordingly.

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

Path parameters

NameTypeRequiredDescription
slugstringyes
surfaceSlugstringyes

Request body

FieldTypeRequiredDescription
positionintegeryesNew 0-indexed tab position. Other tabs shift to accommodate.

Responses

StatusBodyDescription
200objectMoved.

Frequently asked questions

How do I list every tab in a Dock workspace?
GET `/api/workspaces/:slug/surfaces`. Returns each surface's id, slug, name, kind (`table` or `doc`), and tab position. Pass `?archived=true` to include archived surfaces.
How do I add a new tab to a Dock workspace?
POST `/api/workspaces/:slug/surfaces` with `{ kind, name, slug?, columns? }`. Slug auto-derives from name if omitted. For `kind: 'table'`, optionally pass `columns` to override the default Title/Status/Notes triple.
How do I rename a Dock surface?
PATCH `/api/workspaces/:slug/surfaces/:surfaceSlug` with `{ name }`. Pass `{ slug: 'new-slug' }` to also change the URL slug; the old slug stays redirectable. Pass `{ position }` to reorder. Editor role required.
How do I replace the column schema of a Dock table surface?
PATCH `/api/workspaces/:slug/surfaces/:surfaceSlug` with `{ columns: [...] }`. Wholesale replacement: pass the FULL ColumnDef[] you want, server applies it atomically. Each entry: `{ key, label, type, position, width?, hidden?, description?, options? }`. `type` ∈ `text | longtext | url | status | owner | date | number`; `options` is required for `status` and `owner`. Doc/html surfaces 400 with a table-only error. Existing row.data keys not covered by the new columns are preserved on disk (they just stop rendering in the UI until a column with that key is added back). Editor role required. Fires `surface.columns_updated`.
Can I use the MCP or CLI to set columns on an existing Dock surface?
Yes. MCP `update_surface(columns=[...])` mirrors the REST PATCH. CLI: `dock surface set-columns <workspace> <surface-slug> --from cols.json` (or `--json '<array>'`, or pipe stdin). Same `ColumnDef[]` payload across all three. Doc/html surfaces reject in every transport.
How do I delete a tab from a Dock workspace?
DELETE `/api/workspaces/:slug/surfaces/:surfaceSlug`. Soft-archive: rows + doc body preserved for restore. Cannot archive the only live surface; create another first. Editor role required.
How do I reorder Dock surfaces (move a tab)?
POST `/api/workspaces/:slug/surfaces/:surfaceSlug/move` with `{ position }`. 0-based; other tabs shift to keep positions contiguous. Or use PATCH on the same surface with `{ position }`; same effect.
Can a Dock workspace have only doc surfaces (no table)?
Yes. A workspace can hold any combination of table and doc surfaces, one or many of either kind, including all-doc or all-table. The workspace `mode` field is the default-view hint for the first tab opened.
Can I move a Dock surface to a different workspace?
Yes. POST `/api/workspaces/:slug/surfaces/:surfaceSlug/move` with `{ destinationWorkspaceSlug }` (in the same org). All rows / doc body / comments move atomically. Editor on both source and destination required.
How do I tell which surface a row belongs to in Dock?
Every row response includes `surface_slug` (the sheet it lives on). For multi-surface workspaces, `list_rows` and the row API both surface this. Single-sheet workspaces (back-compat) show the primary surface's slug.
Can my AI agent target a specific Dock surface when writing rows?
Yes. POST `/api/workspaces/:slug/rows` with `surface` (slug) or `surfaceId` to pick the target. Omit both to fall through to the workspace's primary table surface. MCP `create_row` accepts the same arg shape.
How does Dock attribute who created or archived a surface?
Surface create + archive both stamp `principalId + principalType` on the surface row. The surface list response renders the agent orb / human face for the creator; the archived view shows the archiver. Same attribution model as workspaces and rows.
Updated