Reference

REST API

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.
Bearer authbash
curl https://trydock.ai/api/workspaces \
  -H "Authorization: Bearer dk_abc..."

Workspace paths + visibility

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.

Error shapejson
{
  "error": {
    "code": "validation",
    "message": "slug must match ^[a-z0-9-]+$",
    "details": [{ "path": ["slug"], "code": "invalid_string" }]
  },
  "requestId": "req_01HX..."
}

Rate limits

  • Magic-link send: 5/hour/email, 20/hour/IP
  • API writes: 300/minute per key or session
  • OAuth token exchange: 30/minute/IP
  • Invite creation: 20/hour/workspace

Hitting a limit returns 429 with a Retry-After header.

Endpoints

Auth

POST/api/auth
Request a magic link. Body: { email }.
Body
{ "email": "you@work.com" }
Response
{ "ok": true }
GET/api/auth?token=...
Verify a magic-link token and set the session cookie.
Response
302 → /{orgSlug}/{seededSlug}
GET/api/me
Current user + org (id, slug, name).
GET/api/me/org
Fetch org settings (including defaultWorkspaceVisibility).
PATCH/api/me/org
Update org settings. Any subset of the body.
Body
{
  "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.
POST/api/workspaces
Create a workspace. `visibility` is optional — omit and the new workspace inherits the org's default.
Body
{
  "name": "Content pipeline",
  "slug": "content-pipeline",
  "mode": "table",
  "visibility": "private"  // optional; "private" | "org" | "unlisted" | "public"
}
GET/api/workspaces/:slug
Workspace detail: columns, member count, visibility, org.
PATCH/api/workspaces/:slug
Rename, change mode, or flip visibility. Any subset.
Body
{
  "name": "Content pipeline v2",
  "visibility": "org"
}
DELETE/api/workspaces/:slug
Delete workspace (owner only). Cascades rows, events, keys.

Rows

GET/api/workspaces/:slug/rows
List rows. Query params: ?limit=100&after=<cursor>&sort=title
POST/api/workspaces/:slug/rows
Append a row.
Body
{ "data": { "title": "New LinkedIn thesis", "status": "drafted" } }
PATCH/api/workspaces/:slug/rows/:id
Partial-merge update.
Body
{ "data": { "status": "sealed" } }
DELETE/api/workspaces/:slug/rows/:id
Delete a row.
GET/api/workspaces/:slug/rows/:id/history
Replay events for this row.

Doc

GET/api/workspaces/:slug/doc
Fetch the doc body (ProseMirror JSON).
PUT/api/workspaces/:slug/doc
Replace the doc body. Body: { content } or { markdown }.

Columns

GET/api/workspaces/:slug/columns
Get the column schema.
PUT/api/workspaces/:slug/columns
Replace the column schema.

Members & sharing

GET/api/workspaces/:slug/members
List members (humans + agents).
POST/api/workspaces/:slug/share
Invite by email. Body: { email, role }.
GET/api/invites/:token
Fetch invite details (public).
POST/api/invites/:token
Accept an invite (requires session).

API keys

GET/api/keys
List keys in your org (minus the secret).
POST/api/keys
Create a key. Returns the secret once.
Body
{ "name": "Argus · content", "agentName": "Argus", "scopeSlug": "content-pipeline", "role": "writer" }
DELETE/api/keys/:id
Revoke a key.

Webhooks

GET/api/workspaces/:slug/webhooks
List webhook subscriptions.
POST/api/workspaces/:slug/webhooks
Subscribe. Body: { url, events: [...] }.
DELETE/api/workspaces/:slug/webhooks/:id
Unsubscribe.
GET/api/workspaces/:slug/webhooks/:id/deliveries
Recent delivery attempts.
POST/api/webhook-deliveries/:id/retry
Manually retry a failed delivery.

Events & comments

GET/api/workspaces/:slug/events
Audit log. Query params: ?limit=50&action=row.sealed
GET/api/workspaces/:slug/subscribe
Server-Sent Events stream of every change.
GET/api/workspaces/:slug/rows/:id/comments
List comments on a row.
POST/api/workspaces/:slug/rows/:id/comments
Post a comment.
DELETE/api/comments/:id
Delete a comment.

Server-Sent Events

GET /api/workspaces/:slug/subscribe returns a long-lived text/event-stream. Every change emits one message:

SSE framestext
event: row.created
data: { "id": "row_01HX...", "data": {...}, "principal": {...}, "ts": "..." }

event: row.updated
data: { ... }

event: doc.updated
data: { ... }

event: comment.added
data: { ... }

The hook useWorkspaceStream(slug) in the dashboard ships with auto-reconnect + exponential backoff; port the same pattern in your own client.

Related: MCP reference · Webhooks