Developers · 04
REST API
JSON under /api/v1. Read the workspace, write documentation, tasks and meetings. Spec: /developers/openapi.json.
Conventions
- Base URL:
https://<your-host>/api/v1 - JSON. POST bodies:
Content-Type: application/json - Success:
200{"ok":true,"data":…} - Error:
4xx/5xx{"ok":false,"error":"<code>","message":"…"} - Lists:
datais an array - Path IDs are slugs, not UUIDs
- Machine-readable spec: OpenAPI (also
GET /api/v1/openapi.json, no token)
Endpoints
| Method | Path | Scope | Returns |
|---|---|---|---|
| GET | /me | any valid token | Org + token scopes |
| GET | /projects | projects:read | Projects in this org |
| GET | /projects/:slug | projects:read | One project (includes id) |
| GET | /projects/:slug/specs | specs:read | Spec lineages + latest published version |
| GET | /projects/:slug/components | components:read | Component lineages + latest version |
| GET | /projects/:slug/milestones | milestones:read | Milestones |
| GET | /projects/:slug/tasks | tasks:read | Tasks |
| GET | /projects/:slug/pages | pages:read | Documentation page tree, without bodies |
| GET | /pages/:page_id | pages:read | One page, body as plain text |
| GET | /documents | documents:read | Team-level document library |
| GET | /projects/:slug/documents | documents:read | A project's library |
| GET | /meetings | meetings:read | Meetings you may see |
| GET | /projects/:slug/meetings | meetings:read | A project's meetings |
| GET | /meetings/:meeting_id | meetings:read | One meeting, minutes as text |
| GET | /meetings/:meeting_id/transcript | meetings:read | Verbatim transcript |
| POST | /webhooks/ping | webhooks:manage | Test delivery to a registered webhook |
/documents and /meetings without a project segment cover the team-level library and every meeting in the organisation. Both accept filters: ?category=documentation|research on documents, ?from= and ?to= as ISO date-times on meetings.
Page, document and meeting ids are UUIDs. Projects are still addressed by slug.
Write endpoints
Four scopes write. Everything else on this API reads.
| Method | Path | Scope | Does |
|---|---|---|---|
| POST | /projects/:slug/pages | pages:write | Create a documentation page |
| PATCH | /pages/:page_id | pages:write | Retitle or rewrite a page |
| POST | /documents | documents:write | Add a team-level link |
| POST | /projects/:slug/documents | documents:write | Add a link to a project |
| PATCH | /documents/:document_id | documents:write | Retitle or redescribe an entry |
| POST | /projects/:slug/tasks | tasks:write | Create a task |
| PATCH | /tasks/:task_id | tasks:write | Update a task, including its status |
| POST | /meetings/:meeting_id/minutes | meetings:write | Set or extend minutes |
| POST | /meetings/:meeting_id/notes | meetings:write | Replace transcript notes |
A PATCH touches only the fields you send. Send null to clear a date or a blocked reason; omit the field to leave it alone.
Page bodies and minutes are written as markdown. Headings, bullet lists and numbered lists convert to the app's rich text. Tables and images do not, yet.
POST to minutes and PATCH on a page both take mode. replace is the default and discards what is there; append adds below a divider. Read the page first if you mean to keep any of it.
Who a write is attributed to
Every write records the person the token belongs to: whoever authorized the OAuth grant, or whoever minted the token by hand. Their name appears on the page, task or minutes, and page edits land in the revision history like any other edit.
A token with no user behind it can read but is refused every write with invalid_request. That happens when the person who created it has been removed from the organisation. Mint a new token to fix it.
Assigning a task takes a user id from your organisation, not a name. An id from anywhere else is refused.
What this API will not do
Publish a specification or a component version. Publishing marks every dependent stale and can route an approval, so it keeps one path through the app. Read a spec here, write your reasoning into a document, and let a person publish it.
Reach a private project. Projects with visibility: private are invisible on /api/v1 whatever scopes a token holds, and a request for one is a 404, the same answer as a project that does not exist. Connected assistants at /api/mcp do reach them, for the member who connected them. See the connector docs.
Upload a file. documents:write files links. A file needs a signed upload, which stays in the app.
Delete anything. There is no delete scope and no delete endpoint.
List projects
curl -s \
-H "Authorization: Bearer $PROTOTYPE_TOKEN" \
"$PROTOTYPE_URL/api/v1/projects"const res = await fetch(`${process.env.PROTOTYPE_URL}/api/v1/projects`, {
headers: { Authorization: `Bearer ${process.env.PROTOTYPE_TOKEN}` },
});
const json = await res.json();
if (!json.ok) throw new Error(json.error);
console.log(json.data);import json, os, urllib.request
req = urllib.request.Request(
f"{os.environ['PROTOTYPE_URL']}/api/v1/projects",
headers={"Authorization": f"Bearer {os.environ['PROTOTYPE_TOKEN']}"},
)
print(json.load(urllib.request.urlopen(req))){
"ok": true,
"data": [
{
"slug": "aurc-2026",
"name": "AURC 2026",
"status": "active",
"visibility": "org",
"description": null,
"startDate": "2025-09-01",
"targetDate": "2026-07-12"
}
]
}Specs and components
GET /projects/:slug/specs returns the latest published version only. Drafts are not in the API.
{
"ok": true,
"data": [
{
"slug": "dry-mass",
"name": "Dry mass",
"kind": "mass",
"units": "kg",
"valueType": "number",
"latest": {
"version": "3",
"status": "published",
"value": 4.2,
"publishedAt": "2026-08-22T05:12:00.000Z"
}
}
]
}GET /projects/:slug/components carries latest.healthStatus: ok, stale, under-rated, or blocked.
Nested routes use the project slug. GET /projects/:slug is the one response that also includes id (uuid).
Milestones and tasks
GET /projects/:slug/milestones:
{
"ok": true,
"data": [
{
"name": "Static fire",
"kind": "test",
"status": "planned",
"targetDate": "2026-10-01",
"actualDate": null,
"description": null
}
]
}GET /projects/:slug/tasks:
{
"ok": true,
"data": [
{
"title": "Torque check on the injector plate",
"status": "in_progress",
"priority": "high",
"targetDate": "2026-09-14",
"description": null
}
]
}Errors
| Code | HTTP | Meaning |
|---|---|---|
invalid_token | 401 | Missing, malformed, revoked, or expired |
insufficient_scope | 403 | Token is valid but missing that scope |
not_found | 404 | Unknown path, or slug not in this org |
invalid_request | 400 | Query or body failed validation, or a write was refused for a stated reason |
rate_limited | 429 | Too many requests on this token |
A 400 on a write carries a message you can act on: which field failed, that the body was too large, that the assignee is not in this organisation, or that the token has no author behind it.