HTTP API
The embedded HTTP server that powers the web UI. All endpoints usable directly for scripts, integrations, and third-party clients.
Starting the server
backlog web # http://localhost:8080, opens browser
backlog web --port 3000 # custom port
backlog web --no-browser # don't auto-open
backlog --as ai:claude-code --db /path/to/backlog.db web --port 8080
All API routes live under /api/. The base URL is http://localhost:8080/api/<resource> by default.
The actor resolved at startup is attributed to every write through the API — there is no per-request actor override. To run as a different actor, restart with a different --as flag.
Conventions
TASK-N, bare integer N, or an 8-char ULID prefix.
*_at fields are Unix nanoseconds (int64), not milliseconds. Convert with new Date(ns / 1_000_000) in JavaScript or time.Unix(0, ns) in Go.
backlog web to the public internet.
Response shape
All success responses are JSON. Errors return {"error": "human-readable message"} with a 4xx or 5xx status.
| Code | Meaning |
|---|---|
200 | Success with body |
204 | Success, no body (DELETE operations) |
400 | Invalid request, validation failure, or business-rule violation |
404 | Resource not found |
405 | Wrong HTTP method |
500 | DB error or internal failure |
Pagination
Endpoints that return long lists accept limit and offset query params. GET /api/tasks defaults to limit=50, offset=0 and returns page.total. Other collection endpoints return the full result set with no pagination.
Enums
| Enum | Values |
|---|---|
| Task type | task · bug · issue · improvement · feature · vulnerability · chore · spike · bucket-list |
| Task status | todo · doing · done |
| Priority | Integer 1–5. 1 = critical, 5 = backlog. Default 3. |
| Actor kind | human · ai |
Projects
List projects. Pass include_archived=true to include archived projects.
curl 'http://localhost:8080/api/projects'
curl 'http://localhost:8080/api/projects?include_archived=true'
Create a project. Required: alias (lowercase alphanumeric + hyphens, 1–64 chars), name (1–255 chars).
curl -X POST http://localhost:8080/api/projects \
-H 'Content-Type: application/json' \
-d '{"alias":"app","name":"My App","repo_path":"/code/app"}'
Archive a project. Body: {}.
Restore an archived project.
Permanently delete an archived project. Active projects return 400. Response: 204 No Content.
Tasks
List tasks with optional filters. Returns {"tasks": [...], "page": {"total": N}}.
| Param | Type | Notes |
|---|---|---|
project | string | Project alias |
status | enum | todo · doing · done |
type | enum | Task type |
priority | int | Exact match, 1–5 |
label | string (repeated) | Multiple label= params are ANDed |
search | string | FTS5 — supports prefix (sql*) and boolean (jwt OR csrf) |
sort | string | priority (default) · created · updated · seq |
include_archived | bool | Default false |
limit | int | Default 50 |
offset | int | Default 0 |
curl 'http://localhost:8080/api/tasks?project=app&type=bug&priority=2'
curl 'http://localhost:8080/api/tasks?search=injection*&limit=20'
Create a task. Required: project (alias), title. Optional: description, type, status, priority, assignee, due_date (YYYY-MM-DD or RFC3339), source, external_ref, project_path, labels (string array — auto-created if missing).
curl -X POST http://localhost:8080/api/tasks \
-H 'Content-Type: application/json' \
-d '{"project":"app","title":"Fix login timeout","type":"bug","priority":2,"labels":["auth"]}'
Get a single task. {id} accepts ULID, TASK-N, bare integer, or 8-char prefix. Always returns project, labels, plans, and comments populated.
Partial update. Omitted fields are left unchanged. Accepted: title, description, type, priority, assignee, due_at, due_date, clear_due_at (bool), source, external_ref, project_path. Note: status is not accepted here — use PATCH /api/tasks/{id}/status.
curl -X PATCH http://localhost:8080/api/tasks/TASK-35 \
-H 'Content-Type: application/json' \
-d '{"assignee":"human:bob","priority":1}'
Transition status. Body: {"status": "doing"}. Setting done also sets completed_at.
Soft-archive a task. Sets archived_at. Body: none required.
Restore an archived task.
Add a comment. Body: {"body": "..."}.
Add a plan (v1). Body: {"title": "...", "body": "..."}.
Attach a label by name. Body: {"name": "security"}. Auto-creates the label if it doesn't exist.
Detach a label from a task. Does not delete the label from the project.
Hard delete a task and all its plans, comments, and label associations. Response: 204.
Labels
List all labels. Pass project=<alias> to filter by project.
Create a label. Body: {"project": "<alias>", "name": "security", "color": "#e03e3e"}. Idempotent — returns the existing label if name already exists in that project.
Plans
List plans. Required: task=<id> query param. Returns current version of each plan on the task.
Get the current version of a plan by ULID.
Update a plan (creates new version). Body: {"title": "...", "body": "...", "change_note": "optional note"}.
All versions of a plan in ascending version order. Each entry includes version number, title, body, actor, change note, and timestamp.
Docs
Versioned markdown documents scoped to a project (not a task). Same versioning model as plans.
List docs. Pass project=<alias> to filter by project. Omit for all non-archived projects.
Create a doc. Body: {"project": "<alias>", "title": "...", "body": "..."}.
Get current version of a doc by ULID.
Update a doc (creates new version). Body: {"title": "...", "body": "...", "change_note": "optional"}.
Delete a doc and all its versions. Response: 204 No Content.
All versions of a doc in ascending version order.
Memory
Free-form tagged notes scoped to a project. Mutable (not versioned). Useful for AI agent scratchpads, design rationale, and short-lived context.
List memory entries. Pass project=<alias> to filter. Pass tag=<name> to filter by tag.
Create a memory entry. Body: {"project": "<alias>", "body": "...", "tags": "decision,auth"}.
Update a memory entry in-place (no version history). Body: {"body": "...", "tags": "..."}.
Append content to an existing memory entry. Creates a new version with the old body + newline + new content. Body: {"body": "additional content"}.
Delete a memory entry. Response: 204 No Content.
Attachments
Binary file attachments linked to tasks or docs. Max 10 MiB per file. Stored as blobs in backlog.db.
List attachment metadata. Pass project=<alias> to filter. Omit for all projects. Does not return the blob data.
Upload an attachment. Multipart form with fields: file (the binary), linked_type (task or doc), linked_id (ULID), name (optional — defaults to filename).
curl -X POST http://localhost:8080/api/attachments \
-F 'file=@screenshot.png' \
-F 'linked_type=task' \
-F 'linked_id=01KR8KXJHFGX4CS9MTX2J4B5XV'
Download the attachment blob. Returns the binary with Content-Type and Content-Disposition headers set.
Activity
Append-only event log of all write operations. Newest first. Returns {"events": [...], "total": N}.
| Param | Notes |
|---|---|
project | Project alias — resolved to ID server-side |
kind | Entity kind: task, plan, doc, comment, etc. |
actor | kind:name — parsed as actor_kind + actor_name |
limit | Default 50, max 10000 |
offset | Default 0 |
curl 'http://localhost:8080/api/activity?project=app&actor=ai:claude-code'
Known gaps
These operations are only available via the CLI:
backlog project update— project field updatesbacklog importandbacklog import-findings— workspace and findings importbacklog export— CSV/Markdown/JSON exportbacklog sync— manifest reconciliationbacklog doctor— integrity check and backupbacklog profile— workspace profile management- Comment delete — comments can be listed and created via API but not deleted