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

Identifiers: All entities use 26-character Crockford Base32 ULIDs. Tasks are additionally addressable as TASK-N, bare integer N, or an 8-char ULID prefix.
Timestamps: All *_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.
No authentication. The server is intended for localhost use only. Do not expose 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.

CodeMeaning
200Success with body
204Success, no body (DELETE operations)
400Invalid request, validation failure, or business-rule violation
404Resource not found
405Wrong HTTP method
500DB 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

EnumValues
Task typetask · bug · issue · improvement · feature · vulnerability · chore · spike · bucket-list
Task statustodo · doing · done
PriorityInteger 15. 1 = critical, 5 = backlog. Default 3.
Actor kindhuman · ai

Projects

GET /api/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'
POST /api/projects

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"}'
PATCH /api/projects/{alias}/archive

Archive a project. Body: {}.

PATCH /api/projects/{alias}/unarchive

Restore an archived project.

DELETE /api/projects/{alias}

Permanently delete an archived project. Active projects return 400. Response: 204 No Content.

Tasks

GET /api/tasks

List tasks with optional filters. Returns {"tasks": [...], "page": {"total": N}}.

ParamTypeNotes
projectstringProject alias
statusenumtodo · doing · done
typeenumTask type
priorityintExact match, 1–5
labelstring (repeated)Multiple label= params are ANDed
searchstringFTS5 — supports prefix (sql*) and boolean (jwt OR csrf)
sortstringpriority (default) · created · updated · seq
include_archivedboolDefault false
limitintDefault 50
offsetintDefault 0
curl 'http://localhost:8080/api/tasks?project=app&type=bug&priority=2'
curl 'http://localhost:8080/api/tasks?search=injection*&limit=20'
POST /api/tasks

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 /api/tasks/{id}

Get a single task. {id} accepts ULID, TASK-N, bare integer, or 8-char prefix. Always returns project, labels, plans, and comments populated.

PATCH /api/tasks/{id}

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}'
PATCH /api/tasks/{id}/status

Transition status. Body: {"status": "doing"}. Setting done also sets completed_at.

PATCH /api/tasks/{id}/archive

Soft-archive a task. Sets archived_at. Body: none required.

PATCH /api/tasks/{id}/unarchive

Restore an archived task.

POST /api/tasks/{id}/comments

Add a comment. Body: {"body": "..."}.

POST /api/tasks/{id}/plans

Add a plan (v1). Body: {"title": "...", "body": "..."}.

POST /api/tasks/{id}/labels

Attach a label by name. Body: {"name": "security"}. Auto-creates the label if it doesn't exist.

DELETE /api/tasks/{id}/labels/{name}

Detach a label from a task. Does not delete the label from the project.

DELETE /api/tasks/{id}

Hard delete a task and all its plans, comments, and label associations. Response: 204.

Labels

GET /api/labels

List all labels. Pass project=<alias> to filter by project.

POST /api/labels

Create a label. Body: {"project": "<alias>", "name": "security", "color": "#e03e3e"}. Idempotent — returns the existing label if name already exists in that project.

Plans

GET /api/plans

List plans. Required: task=<id> query param. Returns current version of each plan on the task.

GET /api/plans/{id}

Get the current version of a plan by ULID.

PATCH /api/plans/{id}

Update a plan (creates new version). Body: {"title": "...", "body": "...", "change_note": "optional note"}.

GET /api/plans/{id}/history

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.

GET /api/docs

List docs. Pass project=<alias> to filter by project. Omit for all non-archived projects.

POST /api/docs

Create a doc. Body: {"project": "<alias>", "title": "...", "body": "..."}.

GET /api/docs/{id}

Get current version of a doc by ULID.

PATCH /api/docs/{id}

Update a doc (creates new version). Body: {"title": "...", "body": "...", "change_note": "optional"}.

DELETE /api/docs/{id}

Delete a doc and all its versions. Response: 204 No Content.

GET /api/docs/{id}/history

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.

GET /api/memory

List memory entries. Pass project=<alias> to filter. Pass tag=<name> to filter by tag.

POST /api/memory

Create a memory entry. Body: {"project": "<alias>", "body": "...", "tags": "decision,auth"}.

PATCH /api/memory/{id}

Update a memory entry in-place (no version history). Body: {"body": "...", "tags": "..."}.

PATCH /api/memory/{id}/append

Append content to an existing memory entry. Creates a new version with the old body + newline + new content. Body: {"body": "additional content"}.

DELETE /api/memory/{id}

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.

GET /api/attachments

List attachment metadata. Pass project=<alias> to filter. Omit for all projects. Does not return the blob data.

POST /api/attachments

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'
GET /api/attachments/{id}

Download the attachment blob. Returns the binary with Content-Type and Content-Disposition headers set.

Activity

GET /api/activity

Append-only event log of all write operations. Newest first. Returns {"events": [...], "total": N}.

ParamNotes
projectProject alias — resolved to ID server-side
kindEntity kind: task, plan, doc, comment, etc.
actorkind:name — parsed as actor_kind + actor_name
limitDefault 50, max 10000
offsetDefault 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 updates
  • backlog import and backlog import-findings — workspace and findings import
  • backlog export — CSV/Markdown/JSON export
  • backlog sync — manifest reconciliation
  • backlog doctor — integrity check and backup
  • backlog profile — workspace profile management
  • Comment delete — comments can be listed and created via API but not deleted