← Docs

Daslab API v1

Base URL: https://daslab.run/v1

Conventions

Authentication

All endpoints require a Bearer token unless marked public.

Authorization: Bearer <token>

Tokens are JWTs issued by /auth/login, /auth/register, or OAuth flows. API keys (prefixed dk_) also work as Bearer tokens for programmatic access.

On 401, clients should attempt POST /auth/refresh before prompting re-login.

Responses

Single resource — return the object directly:

{
  "id": "scene_abc",
  "name": "My Workspace",
  "createdAt": "2026-01-15T10:30:00Z"
}

List — wrap in data with optional pagination:

{
  "data": [ ... ],
  "hasMore": true
}

Errors — always the same shape:

{
  "error": {
    "code": "not_found",
    "message": "Job not found"
  }
}

Field Naming

All request and response fields use camelCase. Database column names are internal and never exposed.

Error Codes

HTTPCodeMeaning
400invalid_requestMissing or invalid parameters
401unauthorizedNo token, expired, or invalid
403forbiddenValid token but insufficient access
404not_foundResource doesn't exist
409conflictResource already exists or state conflict
410version_deprecatedRequest to retired /api/* path
429rate_limitedToo many requests (see Retry-After header)
500internal_errorServer error

Versioning

All app endpoints live under /v1/. Requests to the old /api/* prefix return 410 Gone:

{
  "error": {
    "code": "version_deprecated",
    "message": "This endpoint has moved to /v1/. See https://daslab.dev/docs/api"
  }
}

Scenes

Scenes are the core organizational unit. A root scene (no parent) is an organization. Child scenes are workspaces within it.

List scenes

GET /v1/scenes

Query parameters:

  • parent — filter by parent. null for root scenes (orgs), scene ID for children.
  • includeArchived — include archived scenes (default false).

Returns: { data: [Scene] }

Create scene

POST /v1/scenes

Body:

{
  "name": "My Workspace",
  "parentId": "scene_abc",
  "icon": "cube",
  "tint": "5F9EFF",
  "description": "Optional description"
}

Omit parentId to create a root scene (organization). Creator is automatically added as owner.

Returns: Scene (201)

Get scene

GET /v1/scenes/:id

Returns the scene with nested data: members, assets, recent jobs, and child scenes (for root scenes).

Query parameters:

  • includeArchived — include archived child scenes.

Returns: Scene with members, assets, jobs, scenes arrays.

Update scene

PUT /v1/scenes/:id

Body (all fields optional):

{
  "name": "New Name",
  "icon": "star",
  "tint": "FF6B6B",
  "description": "Updated description",
  "settings": { "featureFlags": {} }
}

Returns: Scene

Delete scene

DELETE /v1/scenes/:id

Soft-deletes (archives) the scene. Returns: Scene with archived status.

Reorder scenes

POST /v1/scenes/reorder

Body:

{
  "orders": [
    { "id": "scene_abc", "sortOrder": 0 },
    { "id": "scene_def", "sortOrder": 1 }
  ]
}

Returns: { data: true }

Get agents

GET /v1/scenes/:id/agents

Returns agents available in this scene (built-in + user-created).

Returns: { data: [Agent], defaultAgentId: "..." }

Get commits

GET /v1/scenes/:id/commits

Scene-level audit trail across all jobs.

Query: limit, offset

Returns: { data: [Commit] }

Scene stream (SSE)

GET /v1/scenes/:id/stream?head=<stateHash>

Server-Sent Events stream for real-time scene state sync. The head query parameter is the client's last-known scene state hash. The server computes a tree diff from the client's HEAD to the current HEAD and sends only changed assets.

Events:
EventDescription
commitScene state changed. Contains stateHash, assets (changed), removed (deleted asset IDs), assetGroupOrder. Sent on initial connect (full or diff based on hash match) and whenever the Merkle tree HEAD advances (enrichment, job mutations, user actions).
job_updateEphemeral streaming data for active jobs — console output, tool call progress. NOT a state mutation. Contains jobId, calls.
job_createdNew job started in this scene. Used to auto-subscribe the SSE stream to the new job's Redis channel.
Commit event payload:
{
  "type": "commit",
  "stateHash": "a9d17c97c23ad368",
  "assets": [
    {
      "id": "ast_abc123",
      "name": "My Sheet",
      "type": "google_sheets/spreadsheet",
      "widget_data": { "type": "table", "headers": [...], "rows": [...], "row_ids": [...] }
    }
  ],
  "assetGroupOrder": ["ast_abc123", "ast_def456"],
  "removed": ["ast_old789"]
}

When the client's head matches the server's current state, assets and removed are empty arrays (no changes). The client should update its local hash to stateHash regardless.

Scene history

GET /v1/scenes/:id/history?limit=50

Returns the commit log for a scene — most recent commits first. Each commit includes the actor (user, agent, system), message, timestamp, and changedAssetCount (number of assets that changed in that commit).

Returns: { commits: [Commit] }

Scene diff

GET /v1/scenes/:id/diff?to=<commitHash>

Returns the tree diff between the current HEAD and a specific commit. Includes full asset data for changed assets.

Returns: { diff: { added: [...], removed: [...], changed: [...] } }

Video call token

POST /v1/scenes/:id/call-token

Generate a LiveKit access token for the scene's video room.

Returns: { token, wsUrl, roomName }

Welcome suggestions

GET /v1/scenes/:id/welcome

Server-generated conversation starters for a scene.

Returns: { suggestions: [...] }

POST /v1/scenes/:id/welcome/blacklist

Hide a suggestion. Body: { prompt: "..." }


Jobs

Jobs are agent runs — each job is a conversation with tool calls.

Create job

POST /v1/jobs

Body:

{
  "message": "Analyze our Q4 metrics",
  "sceneId": "scene_abc",
  "agentId": "agent_xyz",
  "fileUrls": ["https://..."],
  "assetIds": ["asset_1", "asset_2"],
  "title": "Q4 Analysis",
  "autoApprove": false,
  "limits": { "maxTurns": 10 },
  "schedule": {
    "trigger": "cron",
    "cron": "0 9 * * 1",
    "timezone": "America/New_York",
    "enabled": true
  }
}

Required: message. Everything else is optional.

Returns: { jobId, status: "created" } (201)

List jobs

GET /v1/jobs

Query: sceneId, limit (default 50), offset

Returns: { data: [Job], hasMore }

Get job

GET /v1/jobs/:id

Returns job with calls and messages. Supports cursor-based polling for real-time updates.

Query: after — call ID cursor, returns only calls after this ID.

Returns: { job: Job, calls: [Call], messages: [Message] }

Update job

PATCH /v1/jobs/:id

Body (all optional):

{
  "autoApprove": true,
  "limits": { "maxTurns": 20 }
}

Returns: Job

Delete job

DELETE /v1/jobs/:id

Returns: { data: true }

Send input

POST /v1/jobs/:id/input

Send a follow-up message, interrupt, or steer a running job.

Body:

{
  "content": "Actually, focus on revenue metrics only",
  "fileUrls": [],
  "agentId": "agent_xyz",
  "mode": "interrupt"
}
mode: "interrupt" (default, waits for current turn) or "steer" (injects guidance mid-turn).

Returns: { status: "input_received" } or { status: "steering_queued" }

Stop job

POST /v1/jobs/:id/stop

Cancels all pending/running calls.

Returns: { status: "stopped", cancelledCalls: 3 }

Job stream (SSE)

GET /v1/jobs/:id/stream

Real-time SSE stream. Query: after (call ID cursor).

Events: sync, update, done, error.

Job commits

GET /v1/jobs/:id/commits

Git-style audit trail for changes made by this job.

Returns: { data: [Commit] }

Tool call actions

Approve, reject, or retry individual tool calls within a job:

POST /v1/jobs/:id/calls/:callId/approve
POST /v1/jobs/:id/calls/:callId/reject
POST /v1/jobs/:id/calls/:callId/retry

Submit client-side tool execution result (contacts, calendar, HomeKit):

POST /v1/jobs/:id/calls/:callId/result

Body: { output?, error?, durationMs? }

Schedule management

PATCH /v1/jobs/:id/schedule

Body:

{
  "trigger": "cron",
  "cron": "0 9 * * 1",
  "timezone": "America/New_York",
  "enabled": true,
  "message": "Updated prompt for scheduled runs"
}

Returns: { schedule: Schedule }

Webhook trigger

POST /v1/jobs/:id/trigger

Auth: x-webhook-secret header (not Bearer token).

Triggers a scheduled job template. Optional JSON body is injected into the prompt context.

Returns: { status: "triggered", childJobId: "..." }


Assets

Assets are connections to external tools and data sources, scoped to a scene.

List assets

GET /v1/scenes/:id/assets

Query:

  • enrich — fetch live widget data (default false)
  • refresh — force refresh cached data

Returns: { data: [Asset], assetGroupOrder: [...] }

Add asset

POST /v1/scenes/:id/assets

Body:

{
  "type": "github_repo",
  "name": "daslab/server",
  "externalId": "daslab/server",
  "externalUrl": "https://github.com/daslab/server",
  "fields": {},
  "accountId": "asset_github_account_123"
}

Returns: Asset (201)

Update asset view config

PUT /v1/scenes/:id/assets/:assetId

Body: { viewConfig: { ... } }

Returns: Asset

Update asset fields

PATCH /v1/scenes/:id/assets/:assetId

Body: { fields?, name?, description? }

Returns: Asset

Remove asset

DELETE /v1/scenes/:id/assets/:assetId

Unlinks the asset from the scene (does not delete the underlying resource).

Returns: { data: true }


Members

Members belong to scenes. Roles: owner, admin, member, viewer.

List members

GET /v1/scenes/:id/members

Returns: { data: [Member] }

Add member

POST /v1/scenes/:id/members

Body:

{
  "email": "alice@example.com",
  "role": "member"
}

Or by userId/githubId. Sends an invite email if the user doesn't exist yet.

Returns: Member (201)

Update member

PUT /v1/scenes/:id/members/:memberId

Body: { role: "admin" }

Returns: Member

Remove member

DELETE /v1/scenes/:id/members/:memberId

Returns: { data: true }


Providers

Providers are external integrations (GitHub, Slack, Google Sheets, etc.).

List providers

GET /v1/providers

Query: sceneId — show connection status relative to this scene.

Returns: { data: [Provider] } — each provider includes connected: boolean and assetTypes.

Browse provider items

GET /v1/providers/:provider/browse

Browse repos, spreadsheets, channels, files, etc.

Query: sceneId (required), type, accountId, parentId, search

Returns: { data: [BrowsableItem], needsConnection?: boolean }

Provider CRUD

POST /v1/providers/:provider/crud

Create, edit, or delete provider-managed items (repos, issues, spreadsheets, search filters).

Body:

{
  "sceneId": "scene_abc",
  "type": "repository",
  "operation": "create",
  "name": "new-repo",
  "accountId": "asset_123",
  "fields": { "private": true }
}

Returns: { item: {...} } for create/edit, { data: true } for delete.

Device authorization

Start device-code auth flow (for providers like OpenAI Codex):

POST /v1/providers/:provider/auth/device/start

Body: { sceneId: "..." }

Returns: { deviceAuthId, userCode, verificationUrl, interval }

Poll status:

GET /v1/providers/:provider/auth/device/poll/:deviceAuthId

Returns: { status: "pending" | "complete" | "failed", email?, assetId? }


Transcription

Single-shot transcription

POST /v1/transcribe

Multipart form: audio (file), context? (string), sceneId?

Returns: { transcript: "..." }

Transcribe from URL

POST /v1/transcribe/url

Body: { url: "https://...", sceneId? }

Returns: { transcript: "..." }

Streaming transcription

Start session:

POST /v1/transcribe/start

Returns: { sessionId: "..." }

Send chunks:

POST /v1/transcribe/chunk

Body: { sessionId, audio }

End and get result:

POST /v1/transcribe/end

Body: { sessionId, context? }

Returns: { transcript: "..." }


Location

Search places

GET /v1/location/search?query=coffee+shops

Returns: { data: [{ placeId, name, address, types, latitude, longitude }] }

Place details

GET /v1/location/place/:placeId

Returns: { placeId, name, address, latitude, longitude, phoneNumber, website, types }

Reverse geocode

GET /v1/location/reverse?lat=37.7749&lng=-122.4194

Returns: { address, city, state, country }


Uploads

Presigned upload

POST /v1/uploads/presign

Body:

{
  "filename": "photo.jpg",
  "contentType": "image/jpeg",
  "hash": "sha256hex..."
}
hash enables content-addressable storage (deduplication). Omit for legacy UUID-based paths.

Returns: { uploadUrl, fileUrl }


Devices

Register for push notifications

POST /v1/devices/register

Body: { token, platform?, appVersion? }

Returns: { data: true }

Unregister

DELETE /v1/devices/:token

Returns: { data: true }


Client Logs

POST /v1/client-logs

Body:

{
  "logs": [
    { "level": "error", "message": "Failed to load scene", "timestamp": "..." }
  ]
}

Returns: { stored: 5 }


Catalog (public)

These endpoints don't require authentication.

Client config

GET /v1/config

Returns: { googleSignIn: boolean }

Feature flags

GET /v1/feature-flags

Returns: { data: [FeatureFlag] }

Asset type definitions

GET /v1/asset-types

Returns: { data: [AssetType] }

Icons and colors

GET /v1/icons?type=scene

Returns: { sceneIcons: [...], tintColors: [...] }

Suggest icon (requires auth)

POST /v1/suggest-icon

Body: { name: "Marketing", type: "scene" }

Returns: { icon: "megaphone", tint: "FF6B6B" }

Provider logo

GET /v1/logo/:provider

Returns: PNG image. Public, cached.


GitHub (internal)

These endpoints support the member-invite UI. They proxy GitHub's API using the authenticated user's GitHub token.

GET /v1/github/suggestions
GET /v1/github/users/search?q=alice&limit=10
GET /v1/github/users/:username

Unversioned Endpoints

These live outside /v1/ by design — they serve infrastructure, browser flows, or external integrations.

Health

GET /health          → { status: "ok" }
GET /health/db       → { status: "ok", time: "..." }

Auth (browser flows)

OAuth redirects and callbacks. Registered with external providers (GitHub, Google, Apple, Slack, Figma, Canva, Upwork). Changing these paths requires updating provider app configs.

POST /auth/register
POST /auth/login
POST /auth/refresh
POST /auth/apple
GET  /auth/me
PATCH /auth/me
GET  /auth/permissions

GET  /auth/github
GET  /auth/github/callback
POST /auth/github/token
GET  /auth/github/link
GET  /auth/github/connections
DELETE /auth/github/connections/:id

GET  /auth/google/signin
GET  /auth/google/signin/callback

GET  /auth/google
GET  /auth/google/callback
POST /auth/google/refresh

GET  /auth/figma
GET  /auth/figma/callback
POST /auth/figma/refresh

GET  /auth/slack
GET  /auth/slack/callback

GET  /auth/canva
GET  /auth/canva/callback
POST /auth/canva/refresh

GET  /auth/upwork
GET  /auth/upwork/callback

POST /auth/openai-codex/refresh

GET  /auth/verify-email
POST /auth/resend-verification
GET  /auth/accept-invite
GET  /auth/forgot-password
POST /auth/forgot-password
GET  /auth/reset-password
POST /auth/reset-password
GET  /auth/change-password
POST /auth/change-password

Webhooks

POST /webhooks/:provider/:channel
POST /webhooks/telegram/bot

Webhook receiver URLs registered with external services. Provider-specific signature verification.

Admin

Internal tools using shared admin key (?key=...). Not part of the public API.

GET /admin/logs
GET /admin/usage
GET /admin/failed-jobs
GET /admin/traces

MCP Discovery

GET /mcp
GET /mcps

Deprecated Paths

All /api/* endpoints return 410 Gone. The following legacy paths are permanently removed:

Old PathReplacement
GET /api/orgs/summaryGET /v1/scenes?parent=null
POST /api/orgs/reorderPOST /v1/scenes/reorder
POST /api/orgsPOST /v1/scenes
PUT /api/orgs/:idPUT /v1/scenes/:id
DELETE /api/orgs/:idDELETE /v1/scenes/:id
GET /api/orgs/:id/fullGET /v1/scenes/:id
GET /api/orgs/:id/members*GET /v1/scenes/:id/members
GET /api/orgs/:id/assets*GET /v1/scenes/:id/assets
/api/orgs/:id/custom-toolsGET /v1/scenes/:id/assets?type=custom_tool (future)
POST /api/openai-codex/auth/POST /v1/providers/openai_codex/auth/device/
POST /api/logsPOST /v1/client-logs
All other /api/Corresponding /v1/ path

Security Model

Every /v1/ endpoint enforces:

  1. Authentication — Bearer token required (except public catalog endpoints).
  2. Authorization — scene membership checked for all scene-scoped resources. Job access requires ownership or membership in the job's scene.
  3. Role enforcement — destructive operations (delete scene, remove member) require owner or admin role.

Admin endpoints use a separate auth model (shared key via query parameter, to be upgraded to proper admin tokens).