branch/work

The API

The same HTTP API the command line tool uses. Nothing is held back for it.

Basics

  • Everything lives under /api/v1. The version is in the path because a command line tool in the wild outlives any given deploy.
  • Requests and responses are JSON.
  • Authenticate with Authorization: Bearer <token>.
  • Errors return a JSON body with error and usually detail. Read detail; it is written for a person.

Getting a token

The easy way is bw auth login, which stores one for you. To do it yourself, use the same device flow.

POST /api/v1/auth/device/start

$ curl -X POST https://branchwork.info/api/v1/auth/device/start \
    -H "Content-Type: application/json" \
    -d '{"client":"my-script"}'

{
  "device_code": "AsZhgcId...",
  "user_code": "7WPC-NBZ7",
  "verification_uri": "https://branchwork.info/device",
  "expires_in": 900,
  "interval": 3
}

Show the user code to the person and send them to the verification URI. Then poll, waiting the number of seconds in interval between attempts.

POST /api/v1/auth/device/token

$ curl -X POST https://branchwork.info/api/v1/auth/device/token \
    -H "Content-Type: application/json" \
    -d '{"device_code":"AsZhgcId..."}'

202  {"status":"pending"}            not decided yet, keep polling
200  {"status":"ok","token":"bw_..."}  approved, here is your token
403  {"error":"denied"}              the person said no
410  {"error":"expired"}             too slow, start again

A device code mints exactly one token. Polling again after collecting it returns expired, because the token is shown once and only once.

Tokens

Tokens start with bw_. They do not expire on a timer but can be revoked at any time under Settings, Devices, which also shows when each one was last used.

A token acts as you for stacks, changes, revisions and reviews. It cannot read your email, change your handle, or delete your account. Treat it like a password: it belongs in a secret store or an environment variable, never in a repository.

Endpoints

GET /api/v1/me

Who the token belongs to. The cheapest way to check a token still works.

{"handle":"nadia","display_name":"Nadia","email":"[email protected]"}

GET /api/v1/stacks

Every stack you can see.

POST /api/v1/stacks

{
  "name": "Search pipeline",
  "repo": "yourhandle/project",
  "base": "main",
  "description": "Parser, planner, ranker"
}

repo has to look like owner/project. You become the stack's owner. Returns 201 with the new stack.

GET /api/v1/stacks/:id/changes

The whole stack in order, bottom first, each with its position, state, revision count, and the file paths at its head revision.

POST /api/v1/changes

{
  "stack_id": "stk_8f56e965",
  "parent_id": "chg_b9d22681",
  "title": "Plan index scans",
  "summary": "Uses the typed nodes from the parser."
}

Omit parent_id for the bottom of the stack. A stack is a line, so a parent can have only one child, and a stack can have only one bottom. Both are enforced.

POST /api/v1/changes/:id/revisions

{
  "message": "Handle quoted phrases",
  "files": [
    {"path": "src/parser.py", "content": "def parse(text):\n    ..."}
  ]
}

Send the full contents of the files this revision touches. Files you do not mention carry forward from the previous revision. Send empty content for a file the change deletes.

Only the change's author can push. If the resulting file set is identical to the current head, no revision is created and the response is 200 with unchanged: true rather than 201.

GET /api/v1/changes/:id

One change with its revisions, its reviews and notes, and a computed diff. Add ?scope=incremental to get only what has landed since your own last review of it - the same computation the website opens on.

{
  "change": {"id": "chg_b9d22681", "title": "...", "state": "open", ...},
  "revisions": [{"id": "rev_...", "index": 1, "message": "...", ...}],
  "scope": {"mode": "incremental", "base_revision": 2, "head_revision": 4},
  "files": [
    {"path": "src/parser.py", "added": 12, "removed": 3, "lines": [
      {"kind": "added", "old_line": null, "new_line": 14, "text": "..."}
    ]}
  ],
  "reviews": [...]
}

GET /api/v1/reviews/pending

Open changes you did not write, where your last review is older than the current head. This is what bw review list prints.

Status codes

  • 200 - fine.
  • 201 - something was created.
  • 202 - accepted, still pending. Only device polling returns this.
  • 400 - your request was wrong. Read detail.
  • 401 - missing or invalid token.
  • 403 - authenticated, but not allowed to do that.
  • 404 - no such thing.
  • 410 - it expired.

Things worth knowing

  • Every response carries x-branchwork-api: 1. Warn your users if that stops matching what you built against.
  • Authorization is checked on the server for every write. The API is not a privileged back door; it enforces exactly what the website does.
  • Be reasonable about request rate. There is no published limit yet, and the way to keep it that way is not to need one.
Something unclear or wrong here? Tell us - that is a bug in the docs, and we treat it like one.