A read API for your roadmaps: the plans, their nodes and every field on them, as JSON. Nothing you put into RoadSnap is trapped here. Pull it into a warehouse, a spreadsheet, a status report or anything else you run — without asking us or exporting by hand.
Getting started
The API is part of the Team plan. A workspace owner creates tokens in the app: Settings › Integrations › API tokens.
A token is shown once, when it is created. We store a one-way hash, so it cannot be shown again. If you lose it, revoke it and issue another.
curl https://roadsnap.app/api/v1/me \
-H "Authorization: Bearer rs_live_…"
{
"workspace": { "id": "…", "name": "Loon Island", "kind": "team" },
"token": { "name": "CI" },
"plan": "team",
"rate_limit": { "limit": 60, "remaining": 59 }
}
That call checks that a token works and shows its workspace.
Authentication
Send it as a bearer token on every request:
Authorization: Bearer rs_live_…
These are choices, not accidents:
- A token is scoped to one workspace, and to reading. It cannot be widened, and no parameter lets a request reach another workspace.
- Revoking is immediate. Anything using a revoked token gets
403at once. - The plan is checked on every request, not when the token was made. A workspace that leaves Team loses API access the same afternoon — the plan is the owner’s.
- Requests are plain HTTPS with no cookies, so a browser can call the API as well as a server. Still, treat a token like a password: never put one in client-side code.
Rate limits
60 requests per minute, per token. Every response carries what is left:
X-RateLimit-Limit: 60
X-RateLimit-Remaining: 57
Go over it and you get 429 with a Retry-After header: the seconds until
the reset. The counter is per token, so two tokens on a busy job means twice the budget —
that is what tokens are for. Need much more? Write to
support@roadsnap.app and say what you are building.
Endpoints
All endpoints are GET and live under https://roadsnap.app/api/v1.
| Endpoint | Returns |
|---|---|
| GET/me | The token’s workspace, plan and remaining rate budget. |
| GET/roadmaps | Every roadmap in the workspace: id, name, version,
updated_at. Newest change first. |
| GET/roadmaps/{id} | One roadmap, with the full document under doc — the same structure the
app stores and an export produces. |
| GET/roadmaps/{id}/nodes | The convenience view: levels, fields and nodes, each
node with its depth and path of ancestor titles. |
curl https://roadsnap.app/api/v1/roadmaps \
-H "Authorization: Bearer rs_live_…"
{
"roadmaps": [
{ "id": "8f3c…", "name": "Product roadmap", "version": 214,
"updated_at": "2026-08-20T21:14:07.442Z" }
]
}
version increments on every saved change. Store it, and you can skip a roadmap that
has not moved since you last looked without pulling the whole document.
The shape of a node
A roadmap is a tree held as a flat list with parent pointers. parent is
null at the top level.
{
"id": "n42",
"parent": "n7",
"title": "Take a card without leaving the page",
"order": 3,
"note": "Markdown. The reasoning, not a restatement of the title.",
"fields": {
"owner": "Sam", "status": "In progress", "priority": "Must",
"value": "High", "effort": "Medium", "release": "MVP",
"target": "Sep 2026", "completed": null
},
"jira": "ACME-2",
"milestone": false,
"archived": false,
"depth": 2,
"path": ["Product", "Billing"]
}
fieldsholds whatever columns the roadmap defines. Readfieldsat the top of the response for their keys, labels, types and options; do not assume the set above.jiraholds the issue key when an item came from, or is linked to, Jira.- On a roadmap synced with Jira, the
ownerfield (labelled Assignee) holds the assignee’s Atlassian account id; its definition shows"source": "jira". The API returns the id, never the name; resolve it in your own Jira if you need one. depthandpathare computed, not stored, and appear only on the/nodesendpoint.- A node with no children is a leaf; progress is counted on leaves. Parents carry no status of their own.
Errors
Every error has one shape: a type a script can branch on, a message a person can
act on:
{
"error": {
"type": "rate_limited",
"message": "More than 60 requests in a minute. Try again in 24 seconds.",
"docs": "https://roadsnap.app/api.html"
}
}
| Status | Type | What it means |
|---|---|---|
| 401 | no_token | No Authorization header was sent. |
| 401 | bad_token | The token is not one of ours. Unknown and malformed tokens get the same answer, so a probe learns nothing. |
| 403 | revoked_token | The token was revoked. Issue a new one. |
| 402 | plan_required | The workspace’s owner is not on Team. |
| 404 | not_found | No roadmap with that id in this workspace. Someone else’s roadmap is 404, not 403 — a token from another workspace never learns it exists. |
| 404 | unknown_route | No such endpoint. The list above is complete. |
| 405 | method_not_allowed | Something other than GET. |
| 429 | rate_limited | Over 60 in a minute. See Retry-After. |
| 500 | server_error | Ours. Worth telling us about. |
Stability and versioning
The path carries the version. Anything under /api/v1 keeps its meaning. We will add
fields, which is not a breaking change, so parse defensively and ignore what you do not recognise.
Removing or repurposing anything would arrive as /api/v2.
The API is deliberately read-only. Writing into a plan is a different promise, with its own failure modes — concurrent edits, validation, half-applied changes. Done badly, it would put somebody’s roadmap at stake. Reading is the part that makes your data yours, so it is the part that exists.