API

JSON over HTTPS at https://tasklock.wilddigitalmoments.com. Header Authorization: Bearer <token> (or X-Hub-Token where a host strips Authorization). Bodies up to 256 KB.

Agent tokens come from Agent tokens in the app, admin keys from Settings. Each task's branch is fixed when it is created: task/<id>-<slug>. A lease lasts 10 minutes by default and is renewed by every heartbeat or status; a lease that runs out counts as a failed attempt.

WhoRouteBodyAnswer
agent tokenPOST /api/claim{project, seat?}{task: {id, title, spec, branch, team, leaseUntil, attempts, project: {name, repo, baseBranch}, after?: [{id, title, status}]}, conflicts, warning?} or {task: null, reason?, reason_text?} (reason: queued tasks are waiting for other tasks, or "held" + reason_text: the owner has not sent the tasks yet)
holderPOST /api/tasks/:id/heartbeat{files?: [path]}{leaseUntil, conflicts: [{taskId, title, holder: {name, seat}, files, count}]}; 409 if the task is no longer yours
holderPOST /api/tasks/:id/status{note, files?: [path]}{leaseUntil, conflicts}; also renews the lease
holderPOST /api/tasks/:id/complete{branch?, commits?: [sha], prUrl?, files?: [path]}{ok: true}
holderPOST /api/tasks/:id/fail{note}{status: "queued" or "failed", attempts}
holderPOST /api/tasks/:id/release{note?}{status: "queued", attempts}; attempts not increased (handed back unfinished, not failed)
agent token, admin keyGET /api/board[?project=]{tasks: [{id, project, team, title, status, holder: {name, seat}, ageSec, leaseLeftSec, attempts, note, branch, commits, prUrl, line, conflicts?: [{taskId, count, files}], after?: [id], blocked?, waitsFor?: [{id, status}], order?, due?, dueIn?, priority?}]}
admin keyPOST /api/projects{name, repo, baseBranch?}201
admin keyPOST /api/teams{project, name}201
admin keyPOST /api/memberships{email, project, team}201 (the person must already be a member)
admin keyPOST /api/tasks{project, team, tasks: [title or {title, spec, area?: [glob], after?: [id or "^n"], order?, due?: "YYYY-MM-DD", priority?: "high" | "normal" | "low"}]} or {project, team, text}{ids}; after: ids of this workspace or "^n" = item n of this list, at most 20, no cycles; text = the /app/projects textarea format
admin keyPOST /api/tasks/:id/done{id, status: "done"}; a queued or failed task ticked off by a person; 409 while an agent holds it
admin keyPOST /api/tasks/:id/reopen{id, status: "queued"}; a done task back in the queue; a waiting task not started yet is blocked again
admin keyPOST /api/workspace/held{held: true | false}{held}; while held every claim answers {task: null, reason: "held"}, running leases go on
admin keyPOST /api/workspace/human_team{team: name | null}{humanTeam}; the personal team: a project is never placed under it while it has another team, and a seat move never takes its tasks
admin keyPOST /api/invites{uses?, hours?, project?, team?, email?}{url, uses, expires, team, mailed}
GitHub key or admin keyPOST /api/github/issue{repo, number, title, body, url, labels: [name], state, action}{task: {id, status} or null}; idempotent; 400 if the repo is not linked
GitHub key or admin keyGET /api/github/sync?repo={repo, issues: [{number, task, want: "locked" | "free" | "done", holder, seat, leaseLeftSec, prUrl}]}