REST API for the Dossier planning and build system. All routes under /api. SQLite (default) stores data in ~/.dossier/dossier.db. Migrations run automatically on first use.
- Copy
.env.exampleto.env.local. - Anthropic credential: set
ANTHROPIC_API_KEYor rely on installed Claude CLI settings. - GitHub credential: use Connect GitHub OAuth (
GITHUB_OAUTH_CLIENT_ID) or setGITHUB_TOKEN. - Database: SQLite (default) stores data in
~/.dossier/dossier.db. Migrations run automatically on first use.
- Local:
http://localhost:3000 - All routes are under
/api
All errors return JSON:
{
"error": "error_code",
"message": "Human-readable description",
"details": { "field": ["specific issue"] }
}| HTTP Status | Error Code | When |
|---|---|---|
| 400 | validation_failed | Malformed payload, schema mismatch |
| 404 | not_found | Resource doesn't exist |
| 409 | conflict | Referential integrity error |
| 422 | action_rejected | Action rejected (e.g. code-gen intent) |
| 500 | internal_error | Database or unexpected error |
List all projects.
Response: 200 — Array of project objects
[
{ "id": "uuid", "name": "string", "repo_url": "string|null", "default_branch": "string", "created_at": "string", "updated_at": "string" }
]Create a project.
Request body:
{
"name": "string (required)",
"repo_url": "string|null (optional)",
"default_branch": "string (optional, default: main)"
}Response: 201 — Created project object
Get project details.
Response: 200 — Project object | 404 — Not found
Update project.
Request body: Same as POST, all fields optional
Response: 200 — Updated project object
Returns whether setup is incomplete and which keys are still required.
Response: 200
{
"needsSetup": true,
"missingKeys": ["ANTHROPIC_API_KEY", "GITHUB_TOKEN"],
"configPath": "/home/<user>/.dossier/config",
"anthropicViaCli": false
}Notes:
- Anthropic is considered configured when a key is found in env/config or Claude CLI credentials are available.
- GitHub is considered configured when
resolveGitHubToken()succeeds.
Save API credentials to local config (~/.dossier/config) and inject into process env for immediate use.
Request body:
{
"anthropicApiKey": "sk-ant-...",
"githubToken": "ghp_..."
}Constraints:
- At least one of
anthropicApiKeyorgithubTokenmust be non-empty. - If
githubTokenis provided,DOSSIER_GITHUB_IGNORE_ENVis cleared so the new token is used.
Responses:
200{ "success": true, "configPath": "..." }400{ "success": false, "error": "At least one key is required" }
Returns whether GitHub OAuth is configured server-side.
Response: 200
{ "oauthConfigured": true }Starts GitHub OAuth authorization-code + PKCE flow and redirects to GitHub.
Query params:
return_to(optional): same-origin path to return to (/setup,/, etc.)port(optional): loopback callback port override (used for Electron/CLI loopback flows)
Behavior:
- Sets short-lived HttpOnly cookies for OAuth state/verifier/return path.
- Redirects to
https://github.com/login/oauth/authorizewithscope=repo.
Errors:
503whenGITHUB_OAUTH_CLIENT_IDis not configured.
Handles OAuth callback, exchanges code for token, persists GITHUB_TOKEN, then redirects back.
Success redirect query:
github_oauth=success
Error redirect query (github_error):
access_denied- user canceled authinvalid_state- missing/mismatched state/verifier cookiemisconfigured- OAuth client id missingserver- token exchange or config write failed
Disconnect GitHub by removing stored GITHUB_TOKEN and setting DOSSIER_GITHUB_IGNORE_ENV=1 in config.
Why this matters: if .env.local still has GITHUB_TOKEN, disconnect still behaves as disconnected until reconnect/save.
Returns the current GitHub login for the resolved token.
Responses:
200{ "login": "octocat" }401token invalid/expired503token not configured
Lists repositories for authenticated user.
Response: 200
{
"repos": [
{
"full_name": "owner/repo",
"html_url": "https://github.com/owner/repo",
"clone_url": "https://github.com/owner/repo.git",
"private": true
}
]
}Creates a repository for authenticated user.
Request body:
{
"name": "new-repo",
"private": false
}Constraints:
namerequired, regex:^[a-zA-Z0-9._-]+$
Common statuses:
201created401invalid/expired token422invalid name / already exists503token not configured
Canonical map snapshot: Workflow → WorkflowActivity → Step → Card tree.
Response: 200
{
"project": { "id", "name", "repo_url", "default_branch" },
"workflows": [
{
"id", "project_id", "title", "description", "build_state", "position",
"activities": [
{
"id", "workflow_id", "title", "color", "position",
"steps": [{ "id", "title", "position", "cards": [...] }],
"cards": []
}
]
}
]
}Action history for the project.
Response: 200 — Array of PlanningAction records
Submit planning actions. Validates, applies, and persists. Rejects on first failure.
Request body:
{
"actions": [
{
"id": "uuid (optional)",
"action_type": "createWorkflow|createActivity|createStep|createCard|updateCard|reorderCard|linkContextArtifact|upsertCardPlannedFile|approveCardPlannedFile|upsertCardKnowledgeItem|setCardKnowledgeStatus",
"target_ref": {},
"payload": {}
}
]
}Response: 201 — { "applied": number, "results": [...] } | 422 — Action rejected
Supported action types:
| Action | Description |
|---|---|
createWorkflow |
Create a new workflow in the project |
createActivity |
Create a workflow activity |
createStep |
Create a step within an activity |
createCard |
Create a card in a step or activity |
updateCard |
Update card title, description, status, or priority |
reorderCard |
Move card to new step/position |
linkContextArtifact |
Link a context artifact to a card |
upsertCardPlannedFile |
Create or update a planned file for a card |
approveCardPlannedFile |
Approve or revert a planned file |
upsertCardKnowledgeItem |
Create or update a requirement, fact, assumption, or question |
setCardKnowledgeStatus |
Set status (draft/approved/rejected) on a knowledge item |
Code-generation intents are rejected.
List project artifacts.
Response: 200 — Array of ContextArtifact
Create artifact. Requires at least one of: content, uri, integration_ref.
Request body:
{
"name": "string",
"type": "doc|design|code|research|link|image|skill|mcp|cli|api|prompt|spec|runbook",
"title": "string|null",
"content": "string|null",
"uri": "string|null",
"locator": "string|null",
"mime_type": "string|null",
"integration_ref": "object|null"
}Response: 201 — Created artifact
Get single artifact.
Update artifact. All fields optional.
Delete artifact. Response: 204
All knowledge routes require the card to belong to the project (via workflow → activity).
GET /api/projects/[projectId]/cards/[cardId]/requirementsPOST /api/projects/[projectId]/cards/[cardId]/requirementsPATCH /api/projects/[projectId]/cards/[cardId]/requirements/[itemId]DELETE /api/projects/[projectId]/cards/[cardId]/requirements/[itemId]
GET /api/projects/[projectId]/cards/[cardId]/factsPOST /api/projects/[projectId]/cards/[cardId]/factsPATCH /api/projects/[projectId]/cards/[cardId]/facts/[itemId]DELETE /api/projects/[projectId]/cards/[cardId]/facts/[itemId]
GET /api/projects/[projectId]/cards/[cardId]/assumptionsPOST /api/projects/[projectId]/cards/[cardId]/assumptionsPATCH /api/projects/[projectId]/cards/[cardId]/assumptions/[itemId]DELETE /api/projects/[projectId]/cards/[cardId]/assumptions/[itemId]
GET /api/projects/[projectId]/cards/[cardId]/questionsPOST /api/projects/[projectId]/cards/[cardId]/questionsPATCH /api/projects/[projectId]/cards/[cardId]/questions/[itemId]DELETE /api/projects/[projectId]/cards/[cardId]/questions/[itemId]
Create payload (e.g. requirements):
{
"text": "string",
"status": "draft|approved|rejected (optional)",
"source": "agent|user|imported",
"confidence": "number 0-1 (optional)",
"position": "number (optional)"
}List planned files for a card.
Create planned file.
Request body:
{
"logical_file_name": "string",
"module_hint": "string|null",
"artifact_kind": "component|endpoint|service|schema|hook|util|middleware|job|config",
"action": "create|edit",
"intent_summary": "string",
"contract_notes": "string|null",
"status": "proposed|user_edited|approved (optional)",
"position": "number (optional)"
}Update or approve planned file. Use { "status": "approved" } for approval.
Delete planned file.
File tree for the project. Two modes via source query param.
Query params:
| Param | Values | Description |
|---|---|---|
source |
planned (default) |
Planned files from card_planned_file (intent, not produced code) |
source |
repo |
Actual files from cloned repo (after build); includes diff status |
content |
1 |
With source=repo and path: return file content as text/plain |
diff |
1 |
With source=repo and path: return unified diff vs base branch as text/x-diff |
path |
src/foo.ts |
Required when content=1 or diff=1; file path (with or without leading slash) |
Default (source=planned): Returns hierarchical file tree built from card_planned_file.logical_file_name.
source=repo: Returns file tree from the latest build's cloned repo (feature branch). Requires at least one completed or running build with worktree_root set. Nodes include optional status: added, modified, deleted.
source=repo&content=1&path=...: Returns raw file content. 404 if file not found.
source=repo&diff=1&path=...: Returns git diff base...feature -- path. 404 if file unchanged or not found.
Response (tree): 200 — Array of FileNode:
[
{
"name": "src",
"type": "folder",
"path": "/src",
"status": "modified",
"children": [
{ "name": "index.ts", "type": "file", "path": "/src/index.ts", "status": "added" }
]
}
]Response (content/diff): 200 — text/plain or text/x-diff body. 404 — Error JSON if no build or file not found.
Syncs the local clone's base branch with origin/<default_branch>.
Use this after merging PRs on GitHub so subsequent builds branch from an up-to-date base.
Response:
200{ "success": true, "branch": "main" }
Common failures:
400project missing repo URL / repo not found401GitHub authentication/token failure502upstream sync error
Starts orchestration for a workflow or card scope.
Request body:
{
"scope": "workflow|card",
"workflow_id": "uuid (required when scope=workflow)",
"card_id": "uuid (required when scope=card)",
"trigger_type": "card|workflow|manual (optional)",
"initiated_by": "string (required actor identifier)"
}Response: 202
{
"runId": "uuid",
"assignmentIds": ["uuid"],
"message": "Build started",
"outcome_type": "success"
}When decision/validation issues block immediate execution, response remains structured with outcome_type and message.
Resumes a previously blocked assignment after user input is provided.
Request body:
{
"card_id": "uuid",
"actor": "user (optional)"
}Response: 202
{
"assignmentId": "uuid",
"runId": "uuid",
"message": "Build resumed",
"outcome_type": "success"
}GET /api/projects/[projectId]/orchestration/approvals?run_id=<runId>- list approvals for runPOST /api/projects/[projectId]/orchestration/approvals- create approval request (run_id,approval_type=create_pr|merge_pr,requested_by)GET /api/projects/[projectId]/orchestration/approvals/[approvalId]- read single requestPATCH /api/projects/[projectId]/orchestration/approvals/[approvalId]- resolve request (status=approved|rejected,resolved_by, optionalnotes)
GET /api/projects/[projectId]/orchestration/pull-requests?run_id=<runId>- fetch PR candidate for runPOST /api/projects/[projectId]/orchestration/pull-requests- create candidate (run_id,base_branch,head_branch,title,description)GET /api/projects/[projectId]/orchestration/pull-requests/[prId]- read single candidatePATCH /api/projects/[projectId]/orchestration/pull-requests/[prId]- update status (status=not_created|draft_open|open|merged|closed, optionalpr_url)
Starts a built project's dev server from its local clone and opens a browser tab.
Request body:
{ "projectId": "uuid" }Behavior and constraints:
- Tries ports
3001..3010; returns503if all are occupied. - Returns
409if project repo clone does not exist yet (build at least once first). - Route is disabled on known hosted/cloud runtimes.
- Route is enabled locally in
NODE_ENV=developmentor whenDOSSIER_ALLOW_PROJECT_DEV_SERVER=1.
Response: 200
{
"ok": true,
"message": "Starting project server on port 3003 and opening new tab. Page will load in ~10 seconds."
}- Mutations: All map changes go through the actions endpoint; no direct writes.
- Auth: No auth/RLS; endpoints use anon access (single-user desktop app).
- Database: SQLite only; no Supabase or Postgres.