REST API
Doska REST API v1 for CI and scripts: update diagrams from code, import and check infrastructure, publish Markdown docs from git.
The REST API lets CI pipelines and scripts keep Doska in sync with your repositories: push diagram code to a board, rebuild an infrastructure diagram after terraform apply, check a hand-drawn diagram for drift, and publish a folder of Markdown files as documents. Changes go through the live board, so anyone who has it open sees them at once.
Authentication
Every request needs an API token in the Authorization header:
curl -s https://getdoska.top/api/v1/boards/$BOARD_ID/diagrams \
-H "Authorization: Bearer $DOSKA_TOKEN"Create a token in Settings and API tokens → Access tokens → Create token. A token belongs to one organization, can be limited to some projects, and has a level: read, comment or edit. It never has more rights than its owner, so an edit token still can't change a board where you are a viewer. The details are on the Claude and MCP page.
Tokens work only for /api/v1/* and MCP. Any other address answers 403 with the code token_forbidden. Requests and responses are JSON.
Board and project IDs are in the address bar: https://getdoska.top/b/<boardId> and https://getdoska.top/p/<projectId>.
Diagrams
A diagramId is your own stable name for a diagram on a board: Latin letters, digits, - and _, up to 64 characters. For example, architecture.
Create or update a diagram
PUT /api/v1/boards/:boardId/diagrams/:diagramId · token level edit, board role Editor
Creates the diagram if it doesn't exist, or applies the new code to the existing one with the same rules as the Apply button in the code editor: manual positions and styles are kept.
jq -Rs '{source: ., name: "Architecture"}' docs/architecture.mmd | \
curl -fsS -X PUT "https://getdoska.top/api/v1/boards/$BOARD_ID/diagrams/architecture" \
-H "Authorization: Bearer $DOSKA_TOKEN" -H "Content-Type: application/json" --data-binary @-| Field | Type | Description |
|---|---|---|
source | string | Diagram code: Mermaid (flowchart, architecture-beta, sequenceDiagram), D2 or nwdiag. The language is detected automatically |
name | string, optional | Display name, up to 120 characters |
relayout | boolean, default false | Lay out the whole diagram again, discarding manual positions |
Response: 201 when the diagram was created, 200 when it was updated.
{
"ok": true,
"created": false,
"summary": { "nodesAdded": 1, "nodesUpdated": 0, "nodesRemoved": 0, "edgesAdded": 1, "edgesUpdated": 0,
"edgesRemoved": 0, "groupsAdded": 0, "groupsUpdated": 0, "groupsRemoved": 0,
"moved": 0, "overridesKept": 0, "detached": 0 },
"diagnostics": []
}If the code has errors, the answer is 422 and the board is not changed:
{
"error": "The diagram code has errors — the board was not changed",
"code": "invalid_diagram",
"diagnostics": [{ "line": 2, "col": 6, "severity": "error", "message": "Unclosed node shape \"api\": expected \"]\"" }]
}Read diagrams
GET /api/v1/boards/:boardId/diagrams · token level read
[{ "id": "architecture", "name": "Architecture", "appliedAt": 1761480000000, "hasUnappliedChanges": false }]GET /api/v1/boards/:boardId/diagrams/:diagramId · token level read
Returns { id, name, source, draft, appliedAt, revisions }. source is the last applied code, draft is what is in the code editor now, and revisions is the number of saved revisions.
Delete a diagram
DELETE /api/v1/boards/:boardId/diagrams/:diagramId · token level edit, board role Editor
Removes the diagram and all its elements from the board. Response: { "ok": true }.
Validate code
POST /api/v1/diagrams/validate · any token
Checks code without touching a board. Handy in a pre-commit hook.
jq -Rs '{source: .}' docs/architecture.mmd | \
curl -fsS -X POST https://getdoska.top/api/v1/diagrams/validate \
-H "Authorization: Bearer $DOSKA_TOKEN" -H "Content-Type: application/json" --data-binary @-Response: { ok, diagnostics, stats: { nodes, edges, subgraphs } }; stats is null when the code is invalid.
Infrastructure
Build diagrams from Terraform (terraform show -json or a state file), Kubernetes manifests (including helm template output) or docker-compose files. What ends up on the board is described in Infrastructure.
The request body takes either content with the file text, or files: [{ name, content }] for several files. detail is "overview" (default) or "detailed". The file type is detected automatically; up to 25 MB per file. Only names, types and relations are stored, never attribute values.
Import
POST /api/v1/boards/:boardId/infra/import · token level edit, board role Editor
Builds or updates the diagram (ID infra-<source> unless you pass diagramId; name is optional) and keeps the import on the board for drift checks.
terraform show -json > state.json
jq -Rs '{content: .}' state.json | \
curl -fsS -X POST "https://getdoska.top/api/v1/boards/$BOARD_ID/infra/import" \
-H "Authorization: Bearer $DOSKA_TOKEN" -H "Content-Type: application/json" --data-binary @-{ "ok": true, "source": "terraform", "diagramId": "infra-terraform",
"summary": { "nodesAdded": 12, "…": 0 }, "warnings": [],
"drift": { "inSync": true, "bound": 13, "total": 13, "missing": [], "gone": [], "missingLinks": [], "extraLinks": [] } }Check for drift
POST /api/v1/boards/:boardId/infra/check · token level edit, board role Editor
Compares the board with the file and keeps the file as the reference; the diagram itself isn't changed. With notify: true, a comment is posted on the board when it is out of date. The same comment is not posted twice in a row.
Response: { ok, source, warnings, drift, commentThreadId }. In drift:
| Field | Meaning |
|---|---|
inSync | true when there are no differences |
missing | Real resources not on the board: [{ id, name, kind }] |
gone | Board elements bound to resources that no longer exist: [{ elementId, resource, label }] |
missingLinks | Connections not on the board: [{ from, to, kind }] |
extraLinks | Connections on the board that aren't in the infrastructure: [{ connectorId, from, to }] |
To fail a CI step when the board is out of date, pipe the response to jq -e '.drift.inSync'.
List imports
GET /api/v1/boards/:boardId/infra · token level read
Every import on the board with its current drift: [{ source, importedAt, by, detail, diagramId, drift }].
Docs from git
POST /api/v1/projects/:projectId/docs/sync · token level edit, project role Editor
CI sends the Markdown files of a repository folder, and each file becomes a document in the project.
{
"repo": "acme/payments",
"ref": "main",
"sha": "abc1234",
"fileUrl": "https://github.com/acme/payments/blob/main/docs/{path}",
"folder": "Docs",
"prune": false,
"files": [{ "path": "adr/0001-kafka.md", "markdown": "# Use Kafka\n…" }]
}- A document is matched by
repoandpath. New files create documents, changed files update them (the previous text stays in version history), unchanged files are left alone. - The title comes from
title:in front matter, then the first# heading, then the file name. Subdirectories become folders insidefolder. - Relative links between files point to the matching documents.
- With
prune: true, documents of thisrepowhose files are gone are deleted. - Limits: up to 500 files, 1 MB per file and 8 MB in total.
Response: { created, updated, unchanged, removed, docs: [{ path, id, url, status }] }.
The server also hosts a ready-made script with no dependencies (Node 18+). In GitHub Actions it picks up the repository, branch and commit from the environment:
curl -fsSL https://getdoska.top/docs-sync.mjs -o docs-sync.mjs
DOSKA_TOKEN=dsk_… node docs-sync.mjs --project <projectId> --dir docs --folder DocsA ready workflow with your project ID is in the app: open the project, click Create → Docs from git….
Example: GitHub Actions
name: Sync diagrams
on:
push:
paths: ['docs/diagrams/**.mmd']
jobs:
sync:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Push diagrams to Doska
env:
DOSKA_BOARD: ${{ vars.DOSKA_BOARD }}
DOSKA_TOKEN: ${{ secrets.DOSKA_TOKEN }}
run: |
for f in docs/diagrams/*.mmd; do
id=$(basename "$f" .mmd)
jq -Rs --arg n "$id" '{source: ., name: $n}' "$f" | \
curl -fsS -X PUT "https://getdoska.top/api/v1/boards/$DOSKA_BOARD/diagrams/$id" \
-H "Authorization: Bearer $DOSKA_TOKEN" -H "Content-Type: application/json" --data-binary @-
doneGitLab CI uses the same curl command in script:.
Errors
Errors come as JSON with an HTTP status:
{ "error": "Board not found", "code": "not_found" }| Status | Code | When |
|---|---|---|
400 | bad_request, invalid_infrastructure, duplicate_path | The request body or file can't be used |
401 | unauthorized | No token, or the token is revoked, expired or disabled by the organization |
403 | forbidden, token_forbidden | The token level or your role is too low, or the address isn't part of the API |
404 | not_found | The board or project doesn't exist or the token can't see it |
413 | too_large | The request is too large |
422 | invalid_diagram | The diagram code has errors; nothing was changed |
429 | rate_limited | Too many requests; try again in a minute |
A board you can't access answers 404, not 403, so the API doesn't reveal that it exists. Messages are in the token owner's interface language; send X-Doska-Locale: en or ru to choose.
Rate limits
- Up to 600 requests per minute from one address.
- Infrastructure import and check, and docs sync: up to 30 requests per minute each.
- Diagram code up to 200 KB, 2,000 nodes and 5,000 edges.