Base URL: https://trydock.ai. Every endpoint returns JSON. Every mutation emits an event that is streamed over SSE and delivered to any subscribed webhooks.
Authentication
Two ways to authenticate:
Agents: Bearer token (dk_...) in the Authorization header.
Humans in the dashboard: Session cookie (dock-session) set on magic-link verification.
Workspace API paths use just the slug: /api/workspaces/:slug/*. Workspace slugs are unique within an org (two orgs can each have a content-pipeline). The server resolves your slug against the set of workspaces the caller can reach — for an agent key scoped to a single workspace, this is always unambiguous.
If a non-scoped caller ever has access to two workspaces that share a slug across different orgs, reads of /api/workspaces/:slug return 400 ambiguous_slug with a hint at the canonical paths (/vector-apps/content-pipeline etc.). Rare in practice for beta. Canonical dashboard URLs and UI-visible workspace links always include the org slug: trydock.ai/{org}/{workspace}.
Workspace responses include a visibility field. Values: private, org, unlisted, public. See the sharing guide for what each one means. Writes always require explicit membership regardless of visibility.
Request IDs & errors
Every response carries an x-request-id header. Include it when reporting issues.
{
"name": "Vector Apps",
"defaultWorkspaceVisibility": "org" // or "private"
}
DELETE/api/me/sessions
Sign out of every browser this user is signed in on. Does NOT revoke API keys (use /api/keys/:id).
Response
{ "revokedSessions": 3 }
Workspaces
GET/api/workspaces
List workspaces the caller can access. Each row carries a hydrated `createdBy: { principalType, id, name, avatarUrl }` payload so consumers can render creator context without a second round-trip. Null on legacy rows that pre-date the attribution migration. Each row also carries `role` (caller's effective role on this workspace), `pinnedAt` (null if the caller hasn't pinned it), and `archivedAt` (only populated when the list was requested with `?archived=1`). Pass `?archived=1` to list archived workspaces only; the default view excludes them.
POST/api/workspaces
Create a workspace. `visibility` is optional — omit and the new workspace inherits the org's default. For doc-mode workspaces, pass `initialMarkdown` to seed the doc body in the same call (mode auto-resolves to `"doc"` and the default-column scaffolding is skipped). Conversion uses CommonMark + GFM.
Soft-archive a workspace. Editor role required. Stamps `archivedAt + archivedByPrincipalId + archivedByPrincipalType` so the Archived tab can render "archived 2d ago by Argus" without a second lookup. Rows, doc body, members, events stay intact for restore. Use `POST /unarchive` to reverse.
POST/api/workspaces/:slug/unarchive
Restore a soft-archived workspace. Editor role required. Idempotent on already-live workspaces. Emits a `workspace.unarchived` event carrying the previous archive timestamp.
POST/api/workspaces/:slug/pin
Pin this workspace for the calling principal. Writes `WorkspaceMember.pinnedAt = now`. The sidebar Pinned section orders by pinnedAt desc. Pin is a personal preference — one user pinning doesn't pin the workspace for the whole org. Requires an actual membership row (org-visibility virtual viewers can't pin).
Find workspaces, rows, and doc sections across every room the caller can access. `kind` narrows to a single type (`workspace`, `row`, `doc-section`) or defaults to `all`. Substring match (case-insensitive) today; the response shape is stable across the planned pgvector upgrade. Scored 0..1; ties broken by recency. Access-gated via the caller's accessible workspace set. Empty `q` returns 200 with empty hits. Rate-limited at 120/min/principal.
List rows. Query params: ?limit=100&after=<cursor>&sort=title. For multi-sheet workspaces, pass `?surface=<slug>` (or `?surfaceId=<id>`) to scope to one sheet; omit to return rows from every sheet (back-compat).
POST/api/workspaces/:slug/rows
Append a row. Optional `surface` (slug) or `surfaceId` picks which sheet — omit to fall through to the workspace's primary table surface. Position is per-sheet.
Partial-merge update. Setting `surface` (slug) or `surfaceId` to a different sheet MOVES the row there: position recomputes to the destination's tail (override with `position`), and a `row.moved_surface` event fires.
Atomic batch surface move: drop N rows onto a target sheet in one transaction. All-or-nothing — any rowId outside this workspace fails the entire batch. Idempotent: rows already on the target are skipped (returns `skipped` count). Rows land at the destination tail in the order rowIds was supplied. One row.moved_surface event per row that actually moved. Up to 500 rows per call.
Fetch the doc body. Default: ProseMirror JSON in `content`. Add `?format=markdown` for CommonMark+GFM in `markdown`, or `?format=text` for plain text in `text`. Accept: text/markdown + text/plain also negotiated.
PUT/api/workspaces/:slug/doc
Replace the doc body. Body: { content } (ProseMirror JSON) OR { markdown } (CommonMark + GFM, server converts). When both are passed, content wins.
POST/api/workspaces/:slug/doc/sections
Append a markdown chunk to the END of the doc body. Designed for crons + ingest agents that produce content in timestamped chunks (changelog updates, daily summaries, batch reports). Server fetches the current body, splices the new blocks on, writes back through the same path PUT uses. Returns `appendedBlocks` count.
Append a single column. Server auto-computes `position` as next-after-max so the contiguity invariant holds. Key collision returns 409. Use this for per-column additions; use PUT for full replacement or reordering.
Replace the full column schema. Wrap the array in `{ columns: [...] }`. Positions must be contiguous starting at 0.
Members & sharing
GET/api/workspaces/:slug/members
List members (humans + agents). Each user member carries an `inheritedAgents` array — agents signed to that user that read/write the workspace through the inheritance rule, even if they live in a different org.
POST/api/workspaces/:slug/share
Invite by email. Body: { email, role }.
PATCH/api/workspaces/:slug/members/:memberId
Change a member's role. Body: { role: "owner"|"editor"|"commenter"|"viewer" }. When the target is a user, the cascade propagates the new role to every auto-enrolled agent owned by that user on this workspace, atomically. Response carries optional `cascadedAgentIds: string[]` listing the agents that followed.
DELETE/api/workspaces/:slug/members/:memberId
Remove a member. When the target is a user, the cascade also drops every auto-enrolled agent owned by that user on this workspace, in the same transaction — so revoking a guest revokes their fleet. Response carries optional `cascadedAgentIds: string[]`. Sole-owner removal is blocked.
Progress payload for /settings/referrals: code, signed-up count, activated count, days left in current Scale month, recent clicks. Mints a code if one doesn't exist yet.
Self-service
PATCH/api/me
Update your display name and avatar URL.
Body
{ "name": "Govind", "avatarUrl": "https://..." }
POST/api/me/avatar
Upload a profile photo. multipart/form-data, `file` field. Returns a blob URL.
GET/api/me/export
GDPR export: JSON bundle of profile + workspaces + rows + comments + agents + webhooks. Streams as application/json.
POST/api/workspaces/:slug/upload
Upload an image for doc-mode embedding. multipart/form-data, `file` field. 10MB cap, image/* + svg+xml. Returns a blob URL.
POST/api/waitlist
Add an email to the invite-only waitlist. Stable shape across CLI, MCP, and web. Returns 200 with `{ waitlisted: true }` even if the email was previously submitted (idempotent).