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
| HTTP | Code | Meaning |
|---|---|---|
| 400 | invalid_request | Missing or invalid parameters |
| 401 | unauthorized | No token, expired, or invalid |
| 403 | forbidden | Valid token but insufficient access |
| 404 | not_found | Resource doesn't exist |
| 409 | conflict | Resource already exists or state conflict |
| 410 | version_deprecated | Request to retired /api/* path |
| 429 | rate_limited | Too many requests (see Retry-After header) |
| 500 | internal_error | Server 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.nullfor root scenes (orgs), scene ID for children.includeArchived— include archived scenes (defaultfalse).
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.
| Event | Description |
|---|---|
commit | Scene 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_update | Ephemeral streaming data for active jobs — console output, tool call progress. NOT a state mutation. Contains jobId, calls. |
job_created | New job started in this scene. Used to auto-subscribe the SSE stream to the new job's Redis channel. |
{
"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 (defaultfalse)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 Path | Replacement |
|---|---|
GET /api/orgs/summary | GET /v1/scenes?parent=null |
POST /api/orgs/reorder | POST /v1/scenes/reorder |
POST /api/orgs | POST /v1/scenes |
PUT /api/orgs/:id | PUT /v1/scenes/:id |
DELETE /api/orgs/:id | DELETE /v1/scenes/:id |
GET /api/orgs/:id/full | GET /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-tools | GET /v1/scenes/:id/assets?type=custom_tool (future) |
POST /api/openai-codex/auth/ | POST /v1/providers/openai_codex/auth/device/ |
POST /api/logs | POST /v1/client-logs |
All other /api/ | Corresponding /v1/ path |
Security Model
Every /v1/ endpoint enforces:
- Authentication — Bearer token required (except public catalog endpoints).
- Authorization — scene membership checked for all scene-scoped resources. Job access requires ownership or membership in the job's scene.
- Role enforcement — destructive operations (delete scene, remove member) require
owneroradminrole.
Admin endpoints use a separate auth model (shared key via query parameter, to be upgraded to proper admin tokens).