Reference
REST API
A versioned JSON API at /api/v1, for scripts, CI jobs and anything else that
is not an MCP client (D-111). It sits beside /mcp and is the same server:
every endpoint is one MCP tool call or resource read, made with your token.
The bodies are the tools’ own structured results and the errors are the
tools’ own codes and sentences, so docs/DOMAIN.md’s tool table is also this
API’s reference.
On this page
Every one of the 29 tools has exactly one endpoint (accept:4 holds it), and
the contract is an OpenAPI 3.1 document, served without a token at
/api/v1/openapi.json and checked in at docs/api/openapi.json. SPEC-56 is
the acceptance table.
Authentication#
Send a personal API token, the same kind an MCP client uses:
export ROADMAP=https://roadmap.example.com
export ROADMAP_TOKEN=rmp_... # Settings → API tokens on the web
curl -H "Authorization: Bearer $ROADMAP_TOKEN" $ROADMAP/api/v1/projectsA session cookie never authenticates the API, so it has no CSRF surface. A
missing, wrong or revoked token is a 401.
A token can be read-only (the box on the token form): it calls every
endpoint that only reads, and every write answers 403 saying so. Use one for
a dashboard or a CI report.
Each token may make 600 requests a minute by default
(ROADMAP_API_RATE_PER_MINUTE); past that the answer is 429 with
Retry-After. Browser pages on other origins may call the API only when the
operator lists them in ROADMAP_API_CORS_ORIGINS (docs/DEPLOYMENT.md).
Endpoints#
Path wildcards and query parameters are the tool’s own argument names, typed by
its input schema: ?limit=20, ?include_total=true. A parameter the tool does
not take is a 400 that lists the ones it does.
| Endpoint | Tool or resource |
|---|---|
GET /projects | project_list (include_archived) |
GET /projects/{key} | project_get |
GET /projects/{key}/milestones | milestone_list |
GET /roadmap | roadmap://roadmap |
GET /issues | issue_list (project, status, milestone_id, assignee_id, label, type, stale, limit, cursor, include_total) |
GET /issues/search?query= | issue_search |
GET /issues/{key} | issue_get |
GET /issues/{key}/history | issue_history (field, limit, cursor) |
GET /issues/{key}/comments | comment_list (limit, cursor) |
GET /labels | label_list |
GET /types | roadmap://types |
GET /statuses | roadmap://statuses |
GET /users | roadmap://users |
GET /conventions | roadmap://conventions |
GET /guide | roadmap://guide, as text/markdown |
GET /views | roadmap://views |
GET /workspace | roadmap://workspace |
Writes take the tool’s arguments as a JSON object in the body; the path fills
the ones it names, and the body may not repeat them. A create answers 201.
| Endpoint | Tool |
|---|---|
POST /projects | project_create (admin) |
PATCH /projects/{key} | project_update (archiving is admin) |
POST /projects/{key}/milestones | milestone_create |
PATCH /milestones/{id} | milestone_update |
DELETE /milestones/{id} | milestone_delete |
POST /issues | issue_create |
PATCH /issues/{key} | issue_update |
POST /issues/{key}/move | issue_move |
DELETE /issues/{key} | issue_delete |
POST /issues/{key}/comments | comment_add |
PATCH /comments/{id} | comment_update; {"delete": true} deletes |
POST /issues/{key}/proposals | proposal_create |
POST /proposals/{id}/decision | proposal_decide |
POST /labels | label_create |
PATCH /labels/{id} | label_update; {"delete": true} deletes |
POST /types | type_create (admin) |
PATCH /types/{id} | type_update (admin) |
curl -X POST -H "Authorization: Bearer $ROADMAP_TOKEN" -H "Content-Type: application/json" \
-d '{"project":"ENG","title":"Flaky login test","priority":"high"}' \
$ROADMAP/api/v1/issuesEvery write records one event attributed to your account and the token, as an MCP call does.
Files#
| Endpoint | What it does |
|---|---|
POST /issues/{key}/attachments | Upload a file of up to 10 MB as multipart/form-data: a file part, and an optional comment field to attach it to one of the issue’s comments. Answers 201 with attachment_add’s result. |
POST /issues/{key}/attachments/uploads | attachment_upload_url: a one-time link to PUT the raw bytes to yourself, for a client that would rather not send multipart. Complete it with attachment_add’s upload over MCP. |
GET /attachments/{id}/content | The file’s bytes, under the type the server decided, with the web route’s headers: HTML and SVG under a CSP sandbox. |
DELETE /attachments/{id} | attachment_delete: the uploader’s or an administrator’s. |
curl -H "Authorization: Bearer $ROADMAP_TOKEN" -F file=@mock.html $ROADMAP/api/v1/issues/ENG-142/attachments
curl -H "Authorization: Bearer $ROADMAP_TOKEN" -o mock.html $ROADMAP/api/v1/attachments/01J.../contentThe upload streams to the bucket through the same upload link an agent uses,
so it is never held whole in memory. issue_get’s attachments lists an
issue’s files. A server without a bucket answers the file routes with the
sentence naming LITESTREAM_BUCKET.
Lists page with the cursor the tool returns: pass next_cursor back as
?cursor=.
Errors#
Every error has one shape:
{"error": {"code": "not_found", "message": "no issue with key NOPE-9 ..."}}| Code | Status |
|---|---|
bad_request | 400: a query parameter the endpoint does not take, one that is not the type it wants, or a body that is not a JSON object |
unauthorized | 401 |
forbidden | 403: an administrator’s tool, or a write with a read-only token |
not_found | 404 |
method_not_allowed | 405, with Allow |
archived, cross_project, agent_done_not_allowed | 409 |
invalid_argument, proof_required | 422 |
rate_limited | 429, with Retry-After |
internal | 500; the cause is in the server log |