API overview
Base URL, authentication, and response conventions for the REST API.
The REST API is the same surface the web UI and the MCP server both use. Nothing writes anywhere else.
Base URL and auth
The base URL is /api/v1. Every request authenticates with an API key, printed once by pnpm seed.
Authorization: Bearer vt_...Keys come in two scopes, chosen when you create one in Settings.
A workspace key is pinned to the workspace it was made in. Nothing else is reachable with it, and naming another workspace is refused rather than silently ignored.
An account key belongs to you rather than to one workspace, and reaches every workspace you belong to. Each request names the workspace it acts in:
Authorization: Bearer vt_...
X-Voidtrack-Workspace: acmeThe header takes a workspace slug, name, or id, and a ?workspace= query parameter does the same thing. With no workspace named, the call falls back to the key's active workspace (PUT /workspaces/active), then to your only workspace if you belong to exactly one. If it is still ambiguous, the request fails and lists the options rather than guessing. Membership is checked on every call, so losing access to a workspace cuts the key off there immediately.
GET /workspaces lists what a key can reach.
Every response from a call that acts inside a workspace carries an X-Voidtrack-Workspace header naming that workspace. GET /workspaces is the one exception, since listing what a token can reach happens before a workspace is chosen.
Resources
| Resource | Endpoints |
|---|---|
| Teams | GET/POST /teams |
| Issues | GET/POST /issues, GET/PATCH /issues/:identifier |
| Comments | GET/POST /issues/:identifier/comments |
| Projects | GET/POST /projects, PATCH /projects/:id |
| Initiatives | GET/POST /initiatives, PATCH /initiatives/:id |
| Cycles | GET/POST /cycles |
| Labels | GET/POST /labels |
| Users | GET /users |
| Documents | GET/POST /documents, GET/PATCH/DELETE /documents/:id |
| Updates | GET/POST /updates, GET /updates/candidates |
| Relations | GET/POST /issues/:identifier/relations, DELETE /issues/:identifier/relations/:relationId |
Conventions
The API is built to be agent friendly, so most references are by human readable key rather than internal id:
- Teams by key, for example
VOID. - Issues by identifier, for example
VOID-12. - Workflow states by name, for example
In Progress. - Users by email, or
mefor the API key's owner. - Cycles by number, or
current. - Projects and initiatives by name.
- Unknown labels are created on the fly rather than rejected.
Filters and sorting
List endpoints for issues, projects and initiatives take the same filters the app's own views use, so an answer from the API matches what the board shows.
Alongside the plain field filters there are special filters: computed ones, passed comma-separated in special, ANDed with each other and with everything else.
# What is overdue, unassigned, and mine, oldest due date first
curl -s "$VOIDTRACK_URL/api/v1/issues?special=overdue,unassigned&sort=due" \
-H "Authorization: Bearer $VOIDTRACK_API_KEY"| Level | Special filters |
|---|---|
| Issues | overdue, due_today, due_week, no_due_date, stale, unassigned, no_estimate, no_project, no_labels, blocked, blocking, unstarted_overdue, mine, created_by_me |
| Projects | overdue, due_month, no_target_date, stale, no_lead, no_initiative, no_issues, update_overdue, off_track, uncounted, led_by_me |
| Initiatives | overdue, due_quarter, no_target_date, stale, no_lead, no_projects, no_counted_work, update_overdue, off_track, has_excluded, led_by_me |
overdue and stale only ever match open work: something finished is done, late or not. blocked counts only blockers that are themselves still open. uncounted and has_excluded name the canceled and paused work that stays out of progress percentages.
Sorting uses sort, with a - prefix to reverse: issues take priority, due, created, updated, title, estimate, state, assignee; projects take name, target, health, status, updated, progress, scope, lead; initiatives take name, target, health, status, updated, progress, projects. A missing value sorts last ascending and first descending, for every one of these keys.
Example
curl -s localhost:3000/api/v1/issues \
-H "Authorization: Bearer $VOIDTRACK_API_KEY" \
-H "Content-Type: application/json" \
-d '{"team": "VOID", "title": "Ship the MCP server", "priority": 2, "labels": ["mcp"]}'Full reference
Every endpoint, generated straight from the route handlers, is documented in the API Reference section: request and response schemas, parameters, and auth requirements per resource.
Prefer talking to VoidTrack from an agent instead of curl? See MCP.