The flowss REST API lets a script, a CI job, a docs build or your own back end do what the studios do, without a browser. Ten endpoints live under /api/v1/: one generates a diagram from a sentence with AI, and the other nine are deterministic tools that parse, compare, convert, critique and verify diagram source, assemble evidence graphs, and run the Science studio's checkers. Every endpoint authenticates with an account API key that starts with glyp_, answers in JSON, and describes itself when you send it a plain GET. This page is the complete reference: how to get and use a key, the daily allowances and AI points each call spends, every endpoint's request and response, every error you can receive and what to do about it, and ready-to-use recipes.
At a glance
| Fact | Value |
|---|---|
| Base URL | https://www.flowss.ai |
| Endpoints | POST /api/v1/diagram, generate, diff, convert, detect, critique, verify, synthesize, analyze, science |
| Self-describing docs | GET on any endpoint returns its own documentation as JSON, with no key needed |
| Authentication | Authorization: Bearer glyp_… (or X-Api-Key: glyp_…; when both are sent, Authorization is the one read) |
| Where keys come from | Your account page, section API keys, button Create key |
| Format | JSON request bodies (Content-Type: application/json), JSON responses |
| Deterministic endpoints | generate, diff, critique, verify, synthesize, analyze, science: no AI, same input gives the same output |
| AI endpoints | diagram always uses AI; convert and detect use AI only when no built-in method applies |
| Daily allowance (deterministic) | 60 calls per account per day on every plan, shared with the headless render API and some in-app actions |
Daily allowance (diagram) | 60 a day on Free and Starter, 200 on Plus, 400 on Ultra, 500 on Enterprise |
| AI cost | 1 AI point per AI call on Plus, Ultra and Enterprise; your own provider key on Starter and up |
| Day boundary | Midnight UTC |
| Called from | Servers, scripts and CI. The API sends no CORS headers, so a browser page on another site cannot call it directly |
Which plans can use it
Every signed-in account can create API keys. What a key can do depends on the plan of the account that owns it.
| Endpoint | Free | Starter | Plus | Ultra | Enterprise |
|---|---|---|---|---|---|
generate, diff, critique, verify, synthesize, analyze, science | Yes | Yes | Yes | Yes | Yes |
detect (built-in recogniser) | Yes | Yes | Yes | Yes | Yes |
convert (built-in converters) | Yes | Yes | Yes | Yes | Yes |
diagram, and the AI path of convert and detect | No AI | With your own AI key | AI points, or your own key | AI points, or your own key | AI points, or your own key |
Free includes no AI at all: no hosted points and no own-key use. Starter includes no hosted AI points, but you may send your own provider key with each request (see Using your own AI key). Plus, Ultra and Enterprise spend the account's monthly AI points, and may also bring their own key. Plan prices and the full comparison are on Plans and what they include and the pricing page.
Getting an API key
API keys belong to your account, not to a project or a team. All of an account's keys share that account's allowances, so creating more keys does not create more allowance.
- Sign in and open your account page at /account.
- Scroll down to the API keys card. On a wide screen it sits to the right of the Bring-your-own key card; on a narrow one, below it.
- Type a name in the field whose placeholder reads Key name (e.g. CI pipeline). The name is for you; anything past 80 characters is cut off. If you leave it empty the key is called Untitled.
- Click Create key.
- A highlighted box appears: New key — copy now, won't show again. Click Copy (you see "Copied to clipboard") and store the key somewhere safe, such as your CI system's secret store or a password manager.
- Click Dismiss to hide the box.
Warning: The full key is shown exactly once. flowss stores only a one-way fingerprint of it, so nobody, including support, can show it to you again. If you lose it, create a new key and revoke the old one.
What the key list shows
Each key appears as a row under the create field, newest first:
| Part of the row | Meaning |
|---|---|
| Name | The name you gave the key |
Prefix, for example glyp_AbC12… | The first ten characters of the key, so you can tell keys apart without seeing them in full |
| last used date, or never used | The last day a request authenticated with this key |
| created date | When you created it |
| Revoke | Revokes the key |
If you have no keys yet, the list reads No API keys yet. Create one to call /api/render and the /api/v1 endpoints from CI — the render API needs a key. The same keys work for every /api/v1/ endpoint, the headless render API and the flowss MCP server. There is no limit on how many keys you can create.
Revoking a key
- Click Revoke on the key's row.
- Confirm the prompt: Revoke this key? Existing integrations using it will stop working immediately.
- The row disappears and you see Key revoked.
Revocation takes effect on the next request: a revoked key is refused with HTTP 401 from that moment. Keys do not expire on their own; revoke a key when you no longer need it or when it may have been exposed.
Note: At the moment a revoked key can reappear in the list the next time you load your account page, still showing a Revoke button. It stays revoked and every request made with it is refused. Clicking Revoke on it again shows Key not found or already revoked.
Authentication
Send your key on every POST. Any of these forms is accepted by the /api/v1/ endpoints:
Authorization: Bearer glyp_YOUR_KEY
Authorization: glyp_YOUR_KEY
X-Api-Key: glyp_YOUR_KEY
The word Bearer is optional and is not case-sensitive. When a request carries both an Authorization header and X-Api-Key, only Authorization is read, so do not let a proxy add its own Authorization header in front of your key. A value that does not start with glyp_, a key that was revoked, or a key that does not exist are all answered the same way:
{ "error": "Invalid or missing API key. Send 'Authorization: Bearer glyp_<key>'." }
with HTTP status 401.
Note: The headless render API (POST /api/render) reads the key from either header too, and needs one (or a signed-in session): without a key it answers 401sign-in-required, and a key it does not recognise gets 401bad-api-key. On the Free plan it refuses the Lab's engine (glyphscript) with 402engine-plan. See Embedding and the render API.
Keep the key on the server
A key spends your account's allowances and, on the AI endpoint, your AI points. Treat it like a password:
- Store it in your CI system's secrets (for example a repository secret), never in source control.
- Call the API from a server, a script or a CI job. The API sends no cross-origin (CORS) headers, so a web page on another site cannot call it from the browser, and you should never ship a key inside front-end code anyway.
- Use one key per integration, named after it, so you can revoke one without breaking the others and so last used tells you which integrations are alive.
Self-describing endpoints
Every endpoint answers a plain GET with its own documentation as JSON: a description, the body it accepts, options, limits and an example. No key is needed and nothing is counted.
curl -sS https://www.flowss.ai/api/v1/diff | jq .
GET /api/v1/science also lists every operation and every calculation method its results may cite; GET /api/v1/critique and GET /api/v1/verify include a ready-made GitHub Actions workflow in their action field.
Conventions
Requests and responses
- Send
Content-Type: application/jsonand a JSON object as the body. Malformed JSON, or a body that isnull, a number or a string, is refused with HTTP 400 and"Invalid JSON body"(or"Invalid JSON body."oncritiqueandverify). A top-level array is refused the same way byconvert; elsewhere it fails the endpoint's own field checks instead (for exampleMissing 'prompt'). - Every response is JSON. An error response always carries a human-readable
error, and usually a machine-readablecode. - Engine ids are the short ids listed on the engine reference, for example
mermaid,d2,graphviz,plantuml,dbml,sql. The Weave canvas's id islucidflow.
Pass or fail endpoints
critique and verify exist to gate pull requests. They always answer HTTP 200 with pass: true or pass: false when the request was valid, so your pipeline can tell "the diagram did not meet the bar" (a 200 with pass: false) apart from "the request itself failed" (any 4xx or 5xx).
The science ok field
science answers every operation with an ok field. ok: false with HTTP 200 means the request was well formed and the answer is "no" or "declined": a figure with a failing claim, an unbalanceable equation, or a model operation that refused (with a named refusal). HTTP 400 with ok: false and an error means an argument was missing or could not be read, for example a unit the converter does not know, a formula it cannot parse, an unknown journal or guideline id, or an impossible randomisation. One operation answers 404: hazards for a chemical that is not in its reference table.
Findings
critique, verify and analyze return a findings array in one shared shape, the same objects the Studio underlines in its editor and the flowss Studio Agent reports at the end of a run:
| Field | Meaning |
|---|---|
id | A short identifier for the finding |
source | Which checker produced it, for example verify, critique, drift or evidence |
level | error, warning or info |
message | One sentence a person can read |
code | Optional rule code, for example critique/fidelity or drift/missing-entity |
where | Optional location: line, column, range, elementId or subject |
fix | Optional suggested correction, as an object with a label |
detail | Optional list of extra sentences |
evidence | Optional list of provenance-ledger entry ids the finding rests on (science findings only) |
Allowances, rate limits and AI points
The daily allowances
| Allowance | What spends it | Free | Starter | Plus | Ultra | Enterprise |
|---|---|---|---|---|---|---|
| Deterministic calls per day | generate, diff, critique, verify, synthesize, analyze, science, the headless render API with a key, and some in-app actions | 60 | 60 | 60 | 60 | 60 |
| AI generation requests per day | Every diagram call, whether it runs on points or on your own key | 60 | 60 | 200 | 400 | 500 |
| Hosted AI points per month | Every AI call made without your own key | 0 | 0 | 200 | 600 | 600 |
| Own-key AI requests per day | Every AI request carrying your own provider key, across the whole platform | Not available | 1,000 | 1,000 | 1,000 | 1,000 |
The deterministic allowance is the same on every plan: upgrading does not raise it. What a key gives you is an allowance of your own, counted per account, instead of one shared with everybody behind the same internet address.
The deterministic allowance is one counter per account, and several things draw on it:
- every call to the seven deterministic
/api/v1/endpoints above; - every call to the headless render API (
POST /api/render) made with your key; - in the app, each drift Check on a living diagram, and some Evidence actions (rendering, exporting, causal analysis and building a review from a diagram).
detect and convert do not spend the deterministic allowance. When they need AI they spend AI points instead (see below).
Allowances reset at midnight UTC.
When a call is counted
diagramandcritiquevalidate your request first, so a malformed request is refused without spending anything.generate,diff,verify,synthesize,analyzeandsciencecount any request that carries a valid key, before reading the body. A request refused for a missing field or a malformed body has still been counted. Validate locally before you loop over many files.generateanddiffaccept up to 100 files in one request, and a batch counts once. Batch whenever you can.- On
diagram, the daily request ceiling is counted before the AI points check, so a request later refused for lack of points has still used one of the day's requests.
What an AI call costs
| Call | Hosted AI cost | With your own key |
|---|---|---|
diagram | 1 point | No points; counts toward your 1,000 own-key requests a day, and still toward the diagram daily ceiling (60 on Starter) |
convert when no built-in converter applies | 1 point | No points; counts toward your own-key requests |
detect when the built-in recogniser is unsure | 1 point | No points; counts toward your own-key requests |
A request that fails before or during the AI provider call costs no points. On diagram and detect, the point is spent once the provider answers, even if its answer turns out to be unusable; convert charges only for a conversion it actually delivers. Monthly points and top-up packs are explained on AI points and limits.
Using your own AI key
On Starter and up you can pay your AI provider directly instead of spending points. Send your provider key in a second header:
X-User-Api-Key: sk-ant-…
X-User-Ai-Provider: anthropic
| Header | Meaning |
|---|---|
X-User-Api-Key | Your key from a supported AI provider. Used for this request only |
X-User-Ai-Provider | Optional. One of anthropic, openai, google, deepseek, qwen, glm, moonshot, mistral |
If you leave out X-User-Ai-Provider, flowss guesses the provider from the key's shape (Anthropic, Google, Zhipu GLM, OpenAI and Mistral keys are recognisable; a key that matches none is sent to Anthropic). DeepSeek, Qwen and Moonshot keys look like OpenAI keys, so always send the provider header with those. A key whose shape matches no supported provider is refused with bad-key-shape before anything is sent.
Note: A key saved to your account on the Bring-your-own key card of your account page is used by the web app, where you are signed in. API calls carry no sign-in, so a saved key is not used by them: send X-User-Api-Key on every request that should run on your own key. More on Bring your own AI key.
Calls on your own key spend no points but are not unlimited: every own-key AI request on the platform counts toward a ceiling of 1,000 a day per account.
Rate-limit responses
| Status | Body | Returned by | What to do |
|---|---|---|---|
| 429 | {"error":"Rate limit reached (60/day). Try later.","code":"rate-limited"} with header X-Quota-Remaining | diagram, generate, diff, synthesize, analyze, science | Wait for midnight UTC, batch more work per call, or spread runs over days |
| 429 | {"error":"Daily critique quota reached (60/day).","code":"user-cap"} | critique | As above |
| 429 | {"error":"Daily verify quota reached (60/day).","code":"user-cap"} | verify | As above |
| 429 | Daily own-key limit: "code":"rate-limit", "bucket":"byok", with headers X-RateLimit-Bucket and X-RateLimit-Cap | diagram and convert on your own key (detect answers with its low-confidence guess instead) | Wait for midnight UTC |
| 429 | "code":"hosted-ai-account-daily-limit", with resetsAt and a Retry-After header | Hosted AI calls (diagram, the AI path of convert) once your account reaches its daily share of hosted AI | No points were taken. Wait until resetsAt (midnight UTC), or send your own key |
| 503 | {"code":"limiter-unavailable", …} with Retry-After: 30 | diagram | Nothing was run or charged. Retry after the number of seconds in Retry-After |
The number in a 429 message is your actual cap for that allowance, so on Plus the diagram message reads (200/day). A code of ip-cap on critique or verify means the call was counted against your internet address instead of your account, which happens only when account counting is briefly unavailable.
Endpoint reference
| Endpoint | In one line | AI | Spends |
|---|---|---|---|
POST /api/v1/diagram | A sentence in, a complete diagram and an embed link out | Always | 1 point and one AI request |
POST /api/v1/generate | A schema or spec in, a stable Mermaid overview out | Never | One deterministic call |
POST /api/v1/diff | Two versions in, what changed structurally out | Never | One deterministic call |
POST /api/v1/convert | A diagram in one engine in, the same diagram in another out | Only without a built-in converter | Nothing, or 1 point |
POST /api/v1/detect | A snippet in, its engine out | Only when unsure | Nothing, or 1 point |
POST /api/v1/critique | A diagram in, readiness, quality scores and fixes out | Never | One deterministic call |
POST /api/v1/verify | A diagram and its source of truth in, pass or fail out | Never | One deterministic call |
POST /api/v1/synthesize | Claims in, an evidence graph out | Never | One deterministic call |
POST /api/v1/analyze | Claims in, the full evidence analysis out | Never | One deterministic call |
POST /api/v1/science | One of 42 scientific checks and calculations | Never | One deterministic call |
POST /api/v1/diagram
Generates a complete diagram from a natural-language prompt using AI, and returns its source plus a ready-to-embed link.
| Uses AI | Yes, always |
| Cost | 1 AI point, or one own-key request |
| Allowance | AI generation requests per day |
| Plans | Starter (own key only), Plus, Ultra, Enterprise |
| Time limit | 30 seconds |
| Body limit | 250 KB |
Request body:
| Field | Type | Required | Meaning |
|---|---|---|---|
prompt | string | Yes | What to draw. Up to 8,000 characters |
engine | string | No | Preferred engine id. If it is not a valid engine id it is ignored and the AI picks |
curl -sS https://www.flowss.ai/api/v1/diagram \
-H "Authorization: Bearer $FLOWSS_API_KEY" \
-H "Content-Type: application/json" \
-d '{"prompt":"A C4 container diagram for a food-delivery app","engine":"d2"}'
Response (200):
{
"engine": "d2",
"title": "Food delivery — containers",
"code": "…complete d2 source…",
"embed_url": "https://www.flowss.ai/embed?engine=d2&code=…&theme=dark",
"links": {
"embed": "https://www.flowss.ai/embed?engine=d2&code=…&theme=dark",
"render": "https://www.flowss.ai/api/render"
}
}
| Field | Meaning |
|---|---|
engine | The engine the diagram is written for. If the AI names an engine that does not exist, you get your requested engine, or mermaid |
title | A short title, or Diagram if the AI gave none |
code | The complete diagram source |
embed_url | A link to the /embed page that draws this diagram, in the dark theme. Drop it into an <iframe>. Change theme=dark to theme=light if you prefer |
links.render | The headless render API, which turns code into an SVG or PNG file |
Errors specific to this endpoint:
| Status | Message or code | Meaning |
|---|---|---|
| 400 | Missing 'prompt' | The prompt is missing or blank |
| 400 | Prompt too long (>8000 chars) | Shorten the prompt |
| 402 | points-exhausted | Your plan has no AI, Starter has no hosted points, or your points are used up. The message names which, and the body includes your balance, tier, allowance, whether your plan can bring its own key (byok) and the top-up packs (topUp) |
| 402 | byok-upgrade-required | You sent X-User-Api-Key on a Free account. Own keys start on Starter |
| 400 | bad-key-shape | X-User-Api-Key does not look like any supported provider's key. Nothing was sent |
| 429 | rate-limited | The day's diagram requests are used up (Rate limit reached (…/day). Try later.) |
| 429 | rate-limit (bucket byok) | The day's 1,000 own-key AI requests are used up |
| 429 | hosted-ai-account-daily-limit | Your account reached its daily share of hosted AI. No points were taken; wait for midnight UTC or use your own key |
| 502 | The model returned malformed output. | The AI answered with something that was not a diagram, or its answer was cut off. The point is still spent. Try again or rephrase |
| 502 | Generation failed., or Generation failed — followed by a short reason | The AI provider failed or timed out. When the reason is the upstream credentials were rejected and you sent X-User-Api-Key, your provider refused your key: check it and X-User-Ai-Provider |
| 503 | plan-unavailable | Your plan could not be read for a moment. Nothing was charged; retry |
| 503 | hosted-ai-daily-limit or hosted-ai-budget-unavailable | Hosted AI is paused platform-wide for safety. Nothing was charged. Your own key still works |
| 503 | limiter-unavailable | The limits could not be checked. Nothing was run; retry after Retry-After |
POST /api/v1/generate
Turns a structural source (a schema, a spec, a graph) into a stable Mermaid overview. No AI and no network, so the same input always produces byte-identical output: ideal for regenerating a committed diagram in CI on every push.
| Uses AI | No |
| Allowance | Deterministic calls (one per request, batch or single) |
| Time limit | 15 seconds |
| Body limit | 8 MB declared; a single source up to 256 KB (262,144 characters); a batch up to 4 MB of source in total |
Engines it can read: mermaid (flowcharts only), d2, graphviz, dbml, structurizr, sql, erd, openapi, and Weave boards (lucidflow, the board's JSON). The endpoint's own GET lists the first eight; Weave boards are read too. A SQL or DBML schema with columns becomes a Mermaid erDiagram with column types and PK/FK markers (direction does not apply); everything else becomes a Mermaid flowchart whose nodes are numbered n0, n1, … in sorted order, so the output never changes unless the source does.
Single request body:
| Field | Type | Required | Meaning |
|---|---|---|---|
engine | string | Yes | The source's engine id |
code | string | Yes | The source text, up to 256 KB |
direction | string | No | Flowcharts only: TD, TB, LR, RL or BT. Default LR. Anything else is treated as LR |
title | string | No | Written as a %% comment line at the top of the output |
curl -sS https://www.flowss.ai/api/v1/generate \
-H "Authorization: Bearer $FLOWSS_API_KEY" \
-H "Content-Type: application/json" \
-d '{"engine":"sql","code":"CREATE TABLE users (id INT PRIMARY KEY, email TEXT);"}'
Response (200):
{
"engine": "sql",
"ok": true,
"mermaid": "erDiagram\n users {\n INT id PK\n TEXT email\n }\n",
"nodes": 1,
"edges": 0
}
A flowchart example: {"engine":"d2","code":"web -> api\napi -> db","direction":"TD","title":"Overview"} returns a mermaid value of %% Overview, then flowchart TD, then one line per node and per edge.
If the source cannot be parsed, the answer is HTTP 422 with ok: false:
{ "engine": "sql", "parseable_engine": true, "ok": false, "error": "Could not parse this source structurally." }
For an engine with no structural reader, parseable_engine is false and error reads Engine 'pikchr' has no structural parser. (with your engine's id). A Mermaid source that is not a flowchart (a sequence or class diagram, for example) cannot be parsed structurally either.
Batch mode
Send { "files": [ … ] } instead, with up to 100 entries of { path?, engine, code, direction?, title? }. The whole batch counts as one call. In a batch only the 4 MB total is enforced; there is no separate per-file limit.
{
"files": [
{ "path": "db/schema.sql", "engine": "sql", "code": "CREATE TABLE …" },
{ "path": "infra/arch.d2", "engine": "d2", "code": "web -> api", "direction": "TD" }
]
}
Response (200): { "results": [ { "path": "db/schema.sql", "engine": "sql", "ok": true, "mermaid": "…", "nodes": 2, "edges": 1 }, … ], "total": 2, "generated": 2 }. Each result has the same shape as a single response, plus the path you sent. A file that fails to parse is a result with ok: false; it does not fail the batch.
| Status | Message | Meaning |
|---|---|---|
| 400 | Missing 'engine' | Single mode without an engine |
| 400 | 'code' must be a string. | Single mode without source |
| 400 | 'files' is empty. | Batch with no files |
| 400 | Each file needs { engine, code }. | A batch entry is incomplete |
| 413 | Source too large (>256 KB). | Single source too large |
| 413 | Too many files (>100). | Split the batch |
| 413 | Batch too large (>4 MB total). | Split the batch |
POST /api/v1/diff
Compares two versions of a diagram by meaning, not by text: which nodes and edges were added, removed, renamed or relabelled, and for schemas which columns changed. Formatting and whitespace changes are ignored. Built for pull-request comments and merge gates.
| Uses AI | No |
| Allowance | Deterministic calls (one per request, batch or single) |
| Time limit | 15 seconds |
| Body limit | 8 MB declared; 256 KB (262,144 characters) per side in single mode; 4 MB of source per batch |
Structural comparison covers mermaid (flowcharts), d2, graphviz, dbml, structurizr, sql, erd, openapi and Weave boards (lucidflow). Any other engine, a side that does not parse, an empty side, or a pair whose two engines differ falls back to a plain text comparison that ignores only leading and trailing whitespace: you learn whether it changed, but not what.
Single request body:
| Field | Type | Required | Meaning |
|---|---|---|---|
engine | string | Yes | Engine id of the before source |
before | string | Yes | The old source (send an empty string for a new file) |
after | string | Yes | The new source (send an empty string for a deleted file) |
afterEngine | string | No | Engine of the after source, if it changed. Defaults to engine |
format | string | No | markdown adds a ready-to-paste Markdown summary. You can also add ?format=markdown to the URL |
curl -sS https://www.flowss.ai/api/v1/diff \
-H "Authorization: Bearer $FLOWSS_API_KEY" \
-H "Content-Type: application/json" \
-d '{"engine":"d2","before":"a -> b","after":"a -> b\nb -> c"}'
Response (200):
{
"engine": "d2",
"structured": true,
"parseable_engine": true,
"changed": true,
"textChanged": true,
"summary": "Added 1 node (c); added 1 edge (b→c).",
"diff": {
"addedNodes": [{ "id": "c" }],
"removedNodes": [],
"renamedNodes": [],
"addedEdges": [{ "from": "b", "to": "c" }],
"removedEdges": [],
"relabeledEdges": [],
"changedAttributes": [],
"changed": true
}
}
| Field | Meaning |
|---|---|
structured | true when both sides were compared structurally. false means a text comparison was used and diff is null |
parseable_engine | Whether engine is one of the structural engines at all |
changed | Whether anything material changed. For a text comparison, whether the text changed at all once leading and trailing whitespace is trimmed |
textChanged | Whether the source text changed, even if only cosmetically (leading and trailing whitespace ignored) |
summary | A one-line description. No structural change. when nothing material changed; The source changed. or No change. for a text comparison |
diff.addedNodes, removedNodes | Nodes as { id, label? } (schemas also carry attributes) |
diff.renamedNodes | Same id, new label: { id, from, to } |
diff.addedEdges, removedEdges | Edges as { from, to, label? } |
diff.relabeledEdges | Same endpoints, new label: { from, to, fromLabel, toLabel } |
diff.changedAttributes | Schemas only: per table, the columns added, removed and changed (same name, new type or key) |
For a schema the summary reads like a column changelog. Changing users (id INT PRIMARY KEY, age INT) to users (id BIGINT PRIMARY KEY, phone TEXT) summarises as Columns users: +phone, −age, ~id. Long lists in a summary are shortened with +N more (after four nodes or edges, six columns of a table, or three tables); the full lists are always in diff.
With format: "markdown", a single comparison adds a markdown heading (Diagram changed or No structural change) followed by the summary; a batch adds a heading such as 2 of 5 diagrams changed and a table with one row per file (file, engine, change).
Batch mode
Send { "files": [ { path?, engine, before, after, afterEngine? }, … ] } with up to 100 entries. The response is { "results": [ … ], "changed": <number of files that changed>, "total": <number of files> }, each result carrying the path you sent. Add "format": "markdown" to get one markdown table summarising every file, ready to post as a pull-request comment.
| Status | Message | Meaning |
|---|---|---|
| 400 | Missing 'engine' | Single mode without an engine (and without a files array) |
| 400 | Both 'before' and 'after' must be strings. | Send empty strings for added or deleted files |
| 400 | 'files' is empty. / Each file needs { engine, before, after }. | Fix the batch |
| 413 | Source too large (>256 KB). | One side is too large |
| 413 | Too many files (>100). / Batch too large (>4 MB total). | Split the batch |
POST /api/v1/convert
Converts a diagram from one engine to another. Where flowss has a built-in converter for the pair, the result is instant, free and deterministic; otherwise AI rewrites the diagram.
| Uses AI | Only when no built-in converter applies |
| Cost | Built-in: nothing. AI: 1 point, or one own-key request |
| Allowance | Does not spend the deterministic allowance |
| Time limit | 60 seconds |
| Body limit | 512,000 bytes (about 500 KB), checked as the body streams in; source up to 100,000 characters |
Built-in converters:
| From | To |
|---|---|
| Mermaid flowchart | D2, PlantUML |
| D2 | Mermaid |
| Graphviz (DOT) | D2 |
| DBML | Mermaid ER, ERD |
| Markmap | Weave mind map (lucidflow) |
Weave (lucidflow) | Mermaid, Graphviz, D2, PlantUML |
Any structural engine (mermaid flowcharts, d2, graphviz, dbml, structurizr, sql, erd, openapi) | Weave (lucidflow), laid out automatically; notes reads Mapped N nodes and M edges from … onto the Weave canvas. |
A built-in converter only runs on a source it can read: a Mermaid sequence diagram, for example, is not a flowchart, so converting it goes to AI.
Request body:
| Field | Type | Required | Meaning |
|---|---|---|---|
code | string | Yes | The diagram source, up to 100,000 characters |
targetEngine | string | Yes | Engine id to convert to |
sourceEngine | string | No, but recommended | Engine id of code. Without it no built-in converter can run and the conversion always uses AI. Up to 64 characters |
curl -sS https://www.flowss.ai/api/v1/convert \
-H "Authorization: Bearer $FLOWSS_API_KEY" \
-H "Content-Type: application/json" \
-d '{"sourceEngine":"mermaid","targetEngine":"d2","code":"graph LR\n A[API] --> B[(DB)]"}'
Response (200):
{
"engine": "d2",
"code": "direction: right\n\nA: \"API\"\nB: \"DB\" { shape: cylinder }\n\nA -> B",
"notes": "Converted with the built-in mermaid → d2 transpiler (lossless: every node, connection and label carried over).",
"method": "transpiler"
}
method | Meaning |
|---|---|
transpiler | A built-in converter produced it with nothing lost. Also returned, unchanged, when sourceEngine equals targetEngine |
transpiler-lossy | A built-in converter produced it, but something did not carry over. notes names what |
ai | AI rewrote the diagram. notes is the AI's one-sentence account of what carried over and anything dropped |
When the built-in converter would lose structure (a node, a connection, a label), flowss tries AI first, since AI is asked to keep everything. If AI is not available to you (no points, Free plan, a refused or malformed own key, a daily limit, a provider failure), you get the lossy built-in result instead of an error, with method: "transpiler-lossy". When the built-in converter loses only styling, its result is returned straight away as transpiler-lossy without trying AI; notes names what was dropped and suggests converting with AI instead only when structure was lost.
The errors below therefore reach you only when there is no built-in result to fall back on:
| Status | Message or code | Meaning |
|---|---|---|
| 400 | Invalid JSON body | The body is not a JSON object |
| 400 | Missing 'code' field | Send the source |
| 400 | Missing or unknown 'targetEngine' | Use an id from the engine reference |
| 400 | Code is too long (>100 KB) | More than 100,000 characters. Convert a smaller diagram |
| 400 | Source engine name is too long. | sourceEngine over 64 characters |
| 413 | Conversion request is too large. | Body over 512,000 bytes |
| 400 | bad-key-shape | X-User-Api-Key does not look like a supported provider's key |
| 401 | bad-key | Your provider rejected the key you sent in X-User-Api-Key (Your API key was rejected: …) |
| 402 | points-exhausted or byok-upgrade-required | The pair needs AI and your plan or balance cannot pay for it |
| 422 | output-truncated | The converted diagram was too large for one AI reply. Convert a smaller diagram |
| 422 | refusal | The AI declined. Rephrase rather than resending as is |
| 429 | rate-limit (bucket byok) or hosted-ai-account-daily-limit | A daily AI limit was reached |
| 502 | The converter didn't return a usable diagram — try a different target engine. | The AI answer was unusable. No point is charged |
| 503 | plan-unavailable, hosted-ai-daily-limit or hosted-ai-budget-unavailable | A temporary condition. Nothing was charged; retry later |
| 503 | no-key: Converting between these engines needs AI, and this deployment has no AI configured. | AI is not available on this service |
POST /api/v1/detect
Identifies which engine a snippet of diagram source is written for.
| Uses AI | Only when the built-in recogniser is not confident |
| Cost | Built-in: nothing. AI: 1 point, or one own-key request |
| Allowance | Does not spend the deterministic allowance |
| Time limit | 30 seconds |
| Source limit | 100,000 characters; only the first 4,000 are sent to AI |
Request body: { "code": "…" }.
curl -sS https://www.flowss.ai/api/v1/detect \
-H "Authorization: Bearer $FLOWSS_API_KEY" \
-H "Content-Type: application/json" \
-d '{"code":"graph LR\n A --> B"}'
Response (200):
{ "engine": "mermaid", "confidence": "high", "source": "heuristic" }
| Field | Values | Meaning |
|---|---|---|
engine | An engine id | The best match. Always a real engine id |
confidence | high, medium, low | How sure the answer is |
source | heuristic, ai | Whether the built-in recogniser or AI answered |
detect is designed never to block you. If AI would be needed but is not available (no points, the daily limit reached, a provider failure, or an unusable AI answer), it returns its best low-confidence guess, source: "heuristic" and confidence: "low", falling back to mermaid if it has no guess at all. Treat any low answer as a suggestion to confirm.
It returns an error only in these cases: Invalid JSON body (400), Missing 'code' field (400), Code is too long (>100 KB) (400, more than 100,000 characters), and problems with your own AI key that only you can fix: byok-upgrade-required (402, a Free account sent X-User-Api-Key), bad-key-shape (400), bad-key (401, the provider rejected your key) and plan-unavailable (503, your plan could not be read for a moment while you sent your own key; retry). Reaching the daily own-key limit does not produce an error here: you get the low-confidence guess.
POST /api/v1/critique
The full quality read the Studio shows in its status bar, as an API: is the diagram broken, and how good is it across seven dimensions, with the concrete edit that would improve each. No AI, nothing stored, nothing fetched, so the answer is reproducible and costs only one deterministic call.
| Uses AI | No |
| Allowance | Deterministic calls; malformed requests are not counted |
| Time limit | 30 seconds |
| Body limit | 2 MB declared; diagram up to 512 KB |
Request body:
| Field | Type | Required | Meaning |
|---|---|---|---|
engine | string | Yes | Any engine id |
code | string | Yes | The diagram source, non-empty |
request | string | No | The words that asked for this diagram. Supplying them unlocks the fidelity read: does the figure state the numbers the request named, and is it drawn in the notation the request implies? Without it, fidelity is reported as not assessed. Only the first 2,000 characters are used |
gate | string | No | The readiness level pass requires: draft, sound, reviewed or publishable. Default sound |
The readiness levels, lowest to highest:
| Level | Meaning |
|---|---|
empty | Nothing a person would miss |
draft | It exists, and something in it is broken |
sound | Every mechanical check passes |
reviewed | A quality read was taken and it held up |
publishable | It would survive somebody else opening it |
The seven dimensions: fidelity (is this what was asked for?), completeness (the whole thing, or a sketch?), structure (is the shape sound?), legibility (can a reader take it in?), rigour (do the numbers carry their weight?), accessibility (can everyone read it? every colour pair is simulated under three kinds of colour-vision deficiency and in greyscale) and portability (will it survive leaving flowss?).
curl -sS https://www.flowss.ai/api/v1/critique \
-H "Authorization: Bearer $FLOWSS_API_KEY" \
-H "Content-Type: application/json" \
-d '{"engine":"mermaid","code":"flowchart TD\n A[Records identified] --> B[Screened]","request":"PRISMA flow: 1,284 records identified","gate":"sound"}'
Response (200), shortened:
{
"pass": true,
"gate": "sound",
"findings": [
{ "id": "m626ai", "source": "critique", "level": "warning", "code": "critique/fidelity",
"message": "The request reads as PRISMA 2020 flow, and this is drawn in Mermaid.",
"detail": ["Either re-express it in PRISMA 2020 flow, or say in the caption why Mermaid is the better notation here."] }
],
"readiness": {
"level": "sound",
"label": "Sound",
"score": 0.6913,
"reason": "Every mechanical check passes (1 advisory). Quality read: 77% …",
"blocking": [
"Only 2 nodes — this is a stub rather than a model.",
"The request names a figure the diagram never states: 1284."
],
"toAdvance": "Take a quality read and act on the top findings — sound is not the same as good."
},
"verification": { "ok": true, "summary": "Not fully checked — mermaid was not rendered", "defects": [ … ] },
"critique": {
"score": 0.7652,
"band": "solid",
"summary": "77% across 6 dimensions; weakest is fidelity. 1 dimension could not be judged and is left out of the score.",
"unassessed": ["accessibility"],
"dimensions": [
{ "id": "fidelity", "title": "Fidelity", "question": "…", "assessed": true, "score": 0.4, "findings": [ { "message": "…", "fix": "…" } ] }
],
"moves": [
{ "label": "Add the entities the subject actually has; a real system usually has 8 or more.", "why": "Only 2 nodes — this is a stub rather than a model.", "dimension": "completeness" }
]
},
"next": [
{ "capabilityId": "critique-work", "title": "Critique it", "phase": "critique", "why": "…" }
]
}
| Field | Meaning |
|---|---|
pass | true when the readiness level is at or above gate. This is the field a CI job should read |
readiness.blocking | What stands between this diagram and the next level |
readiness.toAdvance | One sentence on how to reach the next level |
findings | Every defect from every checker, in the shared findings shape. Verification problems that block are error; advisory notes and every critique finding are warning |
readiness.level, label, score, reason | The level reached, its display name, a score from 0 to 1 and one sentence explaining it |
verification | The mechanical checks (ok, summary, defects, each defect blocking or advisory). Only verification can make a diagram draft |
critique.score, band, summary | The overall quality score from 0 to 1, a word for it, and one sentence naming the weakest dimension |
critique.dimensions | Each dimension with its score (or null with unassessableBecause when it cannot be judged for this engine) and findings |
critique.unassessed | Dimensions left out of the score. A dimension that cannot be judged is never counted as a pass |
critique.moves | The concrete edits most likely to improve the score |
next | Suggested next steps in flowss, with a route where one applies |
Nothing in critique can fail pass on its own at the default gate: only broken mechanics drop a diagram below sound. Raise gate to reviewed or publishable to hold diagrams to the quality read too. A gate value that is not one of the four levels is treated as sound.
Where an engine has a checker of its own (for example a dependency loop that makes a schedule impossible, or two rules of a decision table that can both fire), its verdict is folded into the read as well.
Note: Some engines are drawn by your browser and cannot be drawn on the server. For those, verification.summary says the diagram was not fully checked, and a warning finding explains which checks did run. The diagram is not treated as broken.
| Status | Message | Meaning |
|---|---|---|
| 400 | Unknown engine "…". Send one of the … engine ids. | Check the id against the engine reference |
| 400 | Send a non-empty 'code'. | Send the source |
| 413 | Diagram too large. | Over 512 KB |
POST /api/v1/verify
An architecture test for CI: checks that a diagram still matches the thing it describes. You send the diagram (inline, or a reference to a figure stored in one of your cloud projects) and the source of truth (a compose file, a SQL schema, an OpenAPI spec or another diagram), and get a deterministic pass or fail.
| Uses AI | No |
| Allowance | Deterministic calls (counted before the body is read) |
| Time limit | 30 seconds |
| Body limit | 2 MB declared; diagram and truth each up to 512 KB |
The body takes one of two shapes.
Stateless, everything inline:
{
"diagram": { "engine": "mermaid", "code": "flowchart LR\n api --> db\n api --> cache[Redis]" },
"truth": {
"kind": "compose",
"content": "services:\n api:\n depends_on: [db, worker]\n db:\n image: postgres:16\n worker:\n image: acme/worker"
}
}
A stored figure, with optional inline truth:
{ "projectId": "<project id>", "figureId": "<figure id>", "truth": { "kind": "sql", "content": "CREATE TABLE …" } }
| Field | Meaning |
|---|---|
diagram.engine | One of mermaid (flowcharts), d2, graphviz, dbml, structurizr, erd, sql, openapi; a Weave board (lucidflow) is read too |
diagram.code | The diagram source, up to 512 KB |
projectId, figureId | A figure in a cloud project the key's owner can open (as a member, or through the project's team). Any role will do, viewer included |
truth.kind | compose, sql, openapi, or a diagram engine: mermaid, d2, graphviz, dbml, structurizr, erd (or lucidflow) |
truth.content | The source of truth's text, up to 512 KB |
If you send diagram and projectId/figureId together, the inline diagram is used.
With a stored figure and no truth, flowss re-fetches the figure's own bound source on the server: the source you attached on the Live dashboard (see Integrations and extensions). The Live dashboard's CI button produces exactly this request with your ids filled in.
Response (200) to the inline example above, where the diagram is missing the worker service and still shows a Redis cache the compose file no longer has:
{
"pass": false,
"summary": "Drift — missing from diagram: worker; no longer in source: Redis.",
"coverage": 0.667,
"truth": "inline compose",
"drift": {
"missingFromDiagram": ["worker"],
"staleInDiagram": ["Redis"],
"missingEdges": [],
"staleEdges": []
},
"findings": [
{ "id": "…", "source": "drift", "level": "error", "code": "drift/missing-entity",
"message": "\"worker\" is in the source of truth but not in the diagram.",
"where": { "subject": "worker" }, "detail": ["Compared against inline compose."] }
]
}
| Field | Meaning |
|---|---|
pass | true when every entity in the truth appears in the diagram, the diagram shows no entity the truth lacks, and every relation in the truth between matched entities is drawn. Extra relations in the diagram (staleEdges) are reported but do not fail the check |
summary | One sentence. The diagram matches the source. when it passes; otherwise Drift — followed by up to five names per list and the number of missing and stale connections |
coverage | Share of the truth's entities found in the diagram, from 0 to 1, rounded to three decimals |
truth | What was compared against: inline <kind>, or a description of the bound source |
drift.missingFromDiagram | In the truth, not in the diagram: the diagram is out of date |
drift.staleInDiagram | In the diagram, not in the truth: the diagram shows something that no longer exists |
drift.missingEdges, staleEdges | The same for relations, as { from, to }, compared only between entities found on both sides |
findings | The same drift as error findings, with codes drift/missing-entity, drift/stale-entity, drift/missing-edge and drift/stale-edge |
How entities are matched: by name, not by internal id. Each node's label (or its id, if it has no label) is lower-cased, parenthetical notes are removed, everything that is not a letter or a digit (spaces included) is dropped, and the affixes service, svc, server and the are removed from the start or end of the name (never from a name only a few characters longer than the affix). So API service matches a compose service called api, and DB matches db. A node labelled Postgres does not match a service called db: name your diagram's nodes after the things in the source.
How relations are matched: a relation is compared only when both of its ends matched, because an edge to a missing entity is already reported as that entity. A relation drawn in the opposite direction from the truth is not reported as stale, but a relation in the truth still counts as missing unless the diagram draws it in the same direction.
Note: Because extra relations do not failpass, a passing response can still carrydrift/stale-edgefindings. If your gate must reject those too, also fail the job whendrift.staleEdgesis not empty.
| Status | Message | Meaning |
|---|---|---|
| 400 | Invalid JSON body. | The body is not a JSON object. This request has still been counted |
| 400 | Send { diagram: {engine, code} } or { projectId, figureId }. | Neither shape was sent |
| 400 | No truth provided and the figure has no source binding — send truth: { kind, content }. | Bind the figure on the Live dashboard, or send truth |
| 404 | Project not found. | The project does not exist or the key's owner cannot open it |
| 404 | Figure not found. | No figure with that id in the project |
| 413 | Diagram too large. / Truth content too large. | Over 512 KB |
| 422 | Engine "…" has no structural parser — verify supports: mermaid, d2, graphviz, dbml, structurizr, erd, sql, openapi. | Use a structural engine for the diagram |
| 422 | Could not parse the truth content as "…". | The truth is malformed, or the wrong kind |
| 422 | A message about the bound source, for example The source is larger than 256 KB, and a drift check needs the whole file — bind a smaller file (for example one schema or one spec per binding). | The bound source could not be fetched or read |
POST /api/v1/synthesize
The Evidence studio's graph engine, without AI. You bring the claims (each a subject, a relation, an object and ideally a supporting quote); flowss assembles the concept graph, finds contradictions and gaps, rates certainty, checks quotes against source text if you supply it, and renders a view.
| Uses AI | No |
| Allowance | Deterministic calls (counted before the body is read) |
| Time limit | 20 seconds |
| Body limit | 8 MB; up to 5,000 claims |
Request body:
| Field | Type | Required | Meaning |
|---|---|---|---|
claims | array | Yes | Each { id?, sourceId?, subject, relation, object, quote?, … }. Optional extras are population, method, effect, n, quoteLoc and arm-level data |
sources | array | No | Source records, for example { id, title?, year? }. A source marked retracted: true stays listed, but its claims are left out (with a warning) |
sourceText | object | No | { "<sourceId>": "full text" }, used to verify each claim's quote |
requireGrounding | boolean | No | With sourceText, drop every claim whose quote cannot be found |
view | string | No | concept-map (default), dag, grade-table or prisma |
appraisals | array | No | Risk-of-bias appraisals: { sourceId, tool: "rob2" or "robins-i", domains or answers } |
Relations: increases, decreases, causes, prevents, treats, correlates, associated, no_effect, moderates, supports, contradicts, part_of. A claim may also carry arm-level data, data: { kind: "binary", events, total, controlEvents, controlTotal } (or continuous or rate), so its effect is computed rather than read from text. GET /api/v1/synthesize lists the exact domain keys each risk-of-bias tool accepts.
curl -sS https://www.flowss.ai/api/v1/synthesize \
-H "Authorization: Bearer $FLOWSS_API_KEY" \
-H "Content-Type: application/json" \
-d '{"claims":[
{"sourceId":"s1","subject":"sleep deprivation","relation":"decreases","object":"working memory","quote":"sleep deprivation decreases working memory"},
{"sourceId":"s2","subject":"sleep deprivation","relation":"no effect","object":"working memory","quote":"no significant effect on working memory"}]}'
Response (200), shortened:
{
"graph": { "concepts": [ … ], "edges": [ … ] },
"claims": [ … ],
"stats": { "concepts": 2, "edges": 2, "claims": 2, "sources": 0, "contradictions": 1 },
"summary": "2 concepts, 2 links, 2 claims, 0 sources; 1 contradiction.",
"contradictions": [ … ],
"contradictionSummary": "1 contradiction: sleep deprivation→working memory (decreases vs no effect on).",
"gaps": [ … ],
"certainty": { "high": 0, "moderate": 0, "low": 0, "very_low": 2, "total": 2 },
"riskOfBias": { "tools": [], "appraised": 0, "total": 2, "unmatched": [] },
"view": { "name": "concept-map", "engine": "mermaid", "code": "…" },
"warnings": []
}
A claim without an id is numbered claim-0, claim-1, … by its position; a claim without a sourceId is grouped with every other unattributed claim under one source, source-unattributed, so unattributed claims never count as independent studies. A claim with no subject or object, or with a relation flowss does not recognise, is dropped and named in warnings (for example claims[3] (…→…): unrecognized relation "…"). warnings also reports retracted sources, records still awaiting a screening decision, several sources that report one study (linked by a shared DOI or trial registration and counted once), and a claim whose quoted effect disagrees with its own arm-level counts.
grounding ({ total, verified, rejected: [{ id, reason }] }) is added when you send sourceText. riskOfBias.appraised: 0 is the honest reading of a synthesis nobody appraised: certainty ratings then state that the study-limitations domain was not assessed. riskOfBias.unmatched names appraisals whose sourceId matches no source. The view.code is diagram source you can render with the render API or open in the Studio.
| Status | Message | Meaning |
|---|---|---|
| 400 | 'claims' must be an array of { subject, relation, object }. | Send a claims array |
| 400 | 'claims' is empty. | Send at least one claim |
| 413 | Too many claims (>5000). | Split the corpus |
| 413 | Payload too large (>8 MB). | Trim sourceText or split the corpus |
POST /api/v1/analyze
Everything synthesize returns, plus the Evidence studio's full analytical layer: the evidence base by study design, leave-one-out robustness, trends over time, a GRADE Summary of Findings and a prioritised research agenda. No AI.
| Uses AI | No |
| Allowance | Deterministic calls (counted before the body is read) |
| Time limit | 30 seconds |
| Body limit | 8 MB; up to 5,000 claims |
Request body: claims (required; each { subject, relation, object, quote?, method?, n?, effect? }), sources (optional; include year for trends over time), sourceText and requireGrounding (as for synthesize). Add method, n and effect to claims for GRADE certainty and effect synthesis.
The response carries every synthesize field plus:
| Field | Meaning |
|---|---|
evidenceBase | Counts by study design (systematic review, RCT, observational, unknown) and a one-line summary |
robustness | For each source, what would change if it were left out: the edges it alone supports, contradictions it creates, and a verdict of load-bearing, contributory or redundant |
temporal | Dated and undated claims, the year span, claims per year and the cumulative growth of the graph |
summaryOfFindings | GRADE Summary of Findings rows grouped by outcome: studies, participants, certainty, risk of bias |
recommendations | A prioritised agenda, for example Resolve conflict: sleep deprivation → working memory with a reason |
findings | Contradictions (error), gaps (warning) and claims dropped for want of grounding (error), in the shared findings shape |
The errors are the same as for synthesize.
POST /api/v1/science
Every checker and calculator the Science studio runs, one operation per request, chosen by the op field. No AI, no network and nothing stored: the same body always gives the same answer, so you can cache results indefinitely.
| Uses AI | No |
| Allowance | Deterministic calls (counted before the body is read) |
| Time limit | 30 seconds |
| Body limit | 2 MB |
Every response includes ok. Send { "op": "…", …arguments }; the op is matched without regard to case or surrounding spaces.
curl -sS https://www.flowss.ai/api/v1/science \
-H "Authorization: Bearer $FLOWSS_API_KEY" \
-H "Content-Type: application/json" \
-d '{"op":"balance","equation":"CH4 + O2 -> CO2 + H2O"}'
{
"ok": true,
"outcome": "balanced",
"coefficients": [1, 2, 1, 2],
"equation": "CH4 + 2O2 -> CO2 + 2H2O",
"message": "Balanced: CH4 + 2O2 -> CO2 + 2H2O",
"species": [ { "formula": "CH4", "writtenCoefficient": 1, "side": "left", "counts": { "C": 1, "H": 4 }, "charge": 0 }, … ]
}
Checking figures and text
op | Send | You get |
|---|---|---|
verify | figure: { nodes: [{ id, data: { label } }], edges: [{ source, target, label }], title?, caption? }, or labels: ["…"] (up to 2,000). Optional include limits the kinds of check to any of equation, formula, units, quantity, constant, identifier, statistics, notation, nomenclature, pathway, consistency | findings (status, message, rule, fix, detail), checked, passed, issues, hasClaims, byKind, score, and ok, which is true only when something was checkable and none of it failed. Claims that are only well-formed identifiers, formulae or units do not count as verified |
notation | text | Notation findings with rule, severity, span and fix; the corrected text; formulae with subscripts; terms that should be italic |
identifier | value (one identifier) or text (free text to scan) | Each identifier's scheme, validity, normalised form, canonical URL and checksum result |
pathway | figure and optional pathwayId, or labels, or query | Coverage of a canonical pathway and findings, or the matching pathways |
taxonomy | name, optional code (ICZN, ICN, ICNP, ICTV) and optional firstMentionDone (true when the full name has already appeared), or query | Name findings with fixes, the corrected name, the organism and its lineage, Markdown and HTML italic forms, or up to 8 matching organisms |
alt-text | figure: { nodes, edges, title? } | Alt text and a long description |
figure-standards | journal and figure: { widthPx, heightPx, column?, fontsPx?, strokeWidthsPx?, format?, fileBytes?, dpi?, artworkType? }. Omit journal to list specs (optionally filtered by query) | Findings with remedies and an export plan (width, height, dpi) |
palette | colours (up to 64) | Colour-difference for every pair under each colour-vision deficiency, problems, greyscale safety and a suggested palette |
accessibility | Any of colours, textPairs: [{ foreground, background, largeText? }], colourIsOnlyChannel, figure | Palette, greyscale and contrast findings, a colour-only warning and alt text |
Journal specs available to figure-standards: nature, science, cell, pnas, plos, elife, elsevier, springer, wiley, ieee, acs, rsc, aps, bmj, lancet, frontiers, mdpi, thesis, poster-a0, slide-16x9.
Chemistry and physics
op | Send | You get |
|---|---|---|
balance | equation | Coefficients, the balanced equation, outcome and species |
redox | equation | Whether it is a redox reaction, the changes, the oxidising and reducing agents |
oxidation-states | formula | Oxidation state of each element, with reasons |
molar-mass | formula, for example CuSO4·5H2O | gramsPerMole (249.677 for that example), per-element contributions and mass percentages |
convert | value (number), from, to | The converted value, its dimension and quantity. 1 eV to J gives 1.602176634e-19, dimension kg·m²·s⁻², quantity energy |
dimension | unit | Canonical form, dimension, exponents, SI factor, quantity name and other names |
check-equation | equation, for example E = 1/2 m v^2 | Whether the dimensions match, the reading of each symbol and a summary. An equation it will not judge returns checkable: false |
quantity | text, for example 9.81 ± 0.02 m/s^2 | Value, unit, uncertainty, coverage factor, significant figures and dimension |
compare | a and b, two measured quantities | Whether they agree, with z-score and normalised error |
constant | symbol, numeric value and optional unit to check a quoted constant, or query to search | Whether the value agrees with the reference value and how closely, or up to 12 matching constants |
spectrum | Optional formula and any of ir, nmr1H, nmr13C (arrays of up to 200 numbers), ms: { molecularIon, peaks: [{ mz, intensity }] } | With a formula: degree of unsaturation, readings per technique, isotope checks, and what is explained, unexplained or contradictory. Without one: each technique read on its own terms |
beer-lambert | Three of absorbance, epsilon, concentration, pathLength | The fourth, with transmittance and working |
nmr-frequency | spectrometerMHz and either ppm or hz | The conversion with working |
hazards | chemical (one name), chemicals (a list) or figure | Hazards, incompatibilities, standing hazards, waste streams, pictograms and PPE, always with a disclaimer. Advisory only: never a substitute for the safety data sheet. A chemical not in the reference table returns 404 with a message saying that this does not mean it is safe |
Study design and reporting
op | Send | You get |
|---|---|---|
critique-design | A design description, for example design: "rct", n, groups, controls, blinding, randomised, outcomes | A verdict, score, findings with remedies, what was not assessed and the working |
randomise | n, groups (a count, or up to 32 labels), optional method (simple, the default, block, stratified-block or minimisation), blockSize, seed, subjects, strata: [{ name, n }], stratifyBy, minimisationProbability | An allocation sequence with group sizes, blocks, balance and warnings. An impossible plan answers 400 with ok: false |
factorial | factors: [{ name, levels }] (up to 16), optional replicates, centrePoints | Cells, run count, runs, estimable effects, error degrees of freedom and a fraction suggestion. An impossible design answers 400 |
replication | technical, biological and optional unit, or unitOfRandomisation, unitOfAnalysis, nested, icc, units | Effective sample size advice, or pseudoreplication detection with the design effect and remedies |
reporting-guideline | studyType and optional stage (protocol or report), or guideline (an id such as consort-2010). Send neither to list every guideline and study design | The primary guideline, additional ones, the risk-of-bias tool and a figure checklist. An unknown guideline answers 400 with the list of valid ids |
risk-of-bias | design, for example rct, cohort, case-control | The right tool and its domains with signalling questions |
Models drawn on a figure, and data
These operations read the models drawn on a Science figure (send the figure's JSON as figure: { nodes, edges, ledger? }), or work on pasted data (tab-separated text with a header row). Every computed number is recorded in a provenance ledger entry that names the method and its version; GET /api/v1/science lists every method in methods.
op | What it does |
|---|---|
model | Reads the models in the figure and runs quick consistency checks |
simulate | Simulates them over time (t1, outputs, infectedIds, seed; series: true adds the time series) |
steady-state | Finds steady states and their stability |
r0 | Computes the basic reproduction number |
fit | Fits a model to data: model is linear, exponential, rate-0, rate-1, rate-2, michaelis-menten, hill, logistic, arrhenius or drawn |
sensitivity | Ranks parameters by elasticity of an output |
montecarlo | Samples parameter uncertainty. seed is required and n may be at most 500; the same body gives a byte-identical answer |
graph | Network measures of the drawing: density, components, degree, centrality, cycles |
describe | Descriptive statistics of a data column, with a normality test |
regress | Linear, logistic or Poisson regression (family), with standard errors, VIF and influence flags |
pi-groups | Dimensionless groups from a list of variables with units, naming known ones such as Re and Fr |
ledger-check | Which ledger entries are current, stale or orphaned |
quantities | The quantities written on the figure's labels, as typed |
Server budgets for these operations: 20,000 integration steps, 20,000 fit evaluations and 500 Monte Carlo runs per request. An operation that hits a budget, lacks a seed or cannot identify a fit answers HTTP 200 with ok: false and a named refusal.
| Status | Message | Meaning |
|---|---|---|
| 400 | Missing 'op'. GET this endpoint for the list of operations. | Send an op |
| 400 | Unknown op "…". GET this endpoint for the list. | The body includes the full list in operations |
| 400 | ok: false with an error naming the missing argument | For example 'equation' is required, e.g. "CH4 + O2 -> CO2 + H2O". |
| 413 | Payload too large (>2 MB). | Send less |
Related endpoints
These also accept your API key and are documented on their own pages.
| Endpoint | What it does | Where to read more |
|---|---|---|
POST /api/render | Turns diagram source into an SVG or PNG file on the server, for engines that can be drawn there. Counts toward the same 60-a-day deterministic allowance | Embedding and the render API |
/embed and embed.js | Live diagrams inside any web page; diagram returns a ready embed_url | Embedding and the render API |
POST /api/mcp | The flowss MCP server for AI assistants and agents; its keyed tools call these endpoints for you | The flowss MCP server |
The web app also calls other internal endpoints. Only the endpoints on this page and the ones above are the public API; anything else may change without notice.
Error reference
Errors shared by several endpoints:
| Status | code | Message | Returned by | What to do |
|---|---|---|---|---|
| 400 | Invalid JSON body (Invalid JSON body. on critique and verify) | Every endpoint | Send a JSON object with Content-Type: application/json | |
| 401 | Invalid or missing API key. Send 'Authorization: Bearer glyp_<key>'. | Every endpoint | Check the header, and that the key was not revoked | |
| 413 | payload-too-large | Request body is too large (>… KB). | diagram (over 250 KB), generate, diff, synthesize (over 8 MB), critique, verify (over 2 MB) | The declared body size is over the endpoint's limit. Nothing was counted |
| 413 | Payload too large (>8 MB). or Payload too large (>2 MB). | synthesize, analyze (8 MB), science (2 MB) | Send less. On analyze and science the call has already been counted | |
| 413 | Conversion request is too large. | convert | Keep the body under 512,000 bytes | |
| 429 | rate-limited, user-cap or ip-cap | Rate limit reached (…/day). Try later., Daily critique quota reached (…/day). or Daily verify quota reached (…/day). | diagram and every deterministic endpoint | The daily allowance is used up; it resets at midnight UTC |
| 503 | limiter-unavailable | We couldn't check the request limits just now, so we haven't run this. Nothing has been charged — please try again in a moment. | diagram | Retry after Retry-After |
Errors from AI calls (diagram, and convert or detect when they need AI). Remember that convert answers with its lossy built-in result instead of most of these when it has one, and detect answers with a low-confidence guess instead of every one except the key problems marked below:
| Status | code | Meaning | Returned by | What to do |
|---|---|---|---|---|
| 402 | points-exhausted | No hosted AI on this plan, or no points left. Body adds balance, tier, allowance, byok (whether your plan may bring its own key) and topUp (the url /account and the top-up packs) | diagram, convert | Buy a top-up pack on /account, wait for your monthly allowance, upgrade, or send your own key (Starter and up) |
| 402 | byok-upgrade-required | You sent your own AI key from a Free account. Body adds tier, source and upgrade | diagram, convert, detect | Own keys need Starter or above |
| 400 | bad-key-shape | That doesn't look like an API key from a supported provider. Nothing was sent. | diagram, convert, detect | Check X-User-Api-Key |
| 401 | bad-key | Your API key was rejected: … (your own provider key) | convert, detect (diagram reports this as a 502, see its table) | Check the key and X-User-Ai-Provider |
| 422 | output-truncated | output too large for this request — try a smaller diagram | convert | Convert a smaller diagram |
| 422 | refusal | The model declined this request. Rephrase it rather than resending it as is. | convert | Rephrase |
| 429 | rate-limit (bucket byok) | Daily limit for AI requests on your own key reached (1000/day). It resets at midnight UTC — nothing was sent to your provider. | diagram, convert | Wait for midnight UTC |
| 429 | hosted-ai-account-daily-limit | Your account reached today's hosted-AI limit. No points were taken. Body has resetsAt and byok | diagram, convert | Wait until resetsAt, or use your own key |
| 502 | The AI provider failed | diagram, convert | Retry with backoff | |
| 503 | plan-unavailable | Your plan could not be read. Nothing was charged | diagram, convert, detect (own key only) | Retry in a moment |
| 503 | hosted-ai-daily-limit | Hosted AI reached its platform-wide daily safety limit. Body has resetsAt and a Retry-After header | diagram, convert | Wait until resetsAt, or use your own key |
| 503 | hosted-ai-budget-unavailable | Hosted AI is briefly unavailable. Nothing was charged. Comes with Retry-After: 60 | diagram, convert | Retry later, or use your own key |
| 503 | no-key | AI is not configured on this service | diagram, convert | Use the deterministic endpoints |
Recipes
A shell script for CI
Fail the build if any architecture diagram is broken, and print why. Requires curl and jq.
#!/usr/bin/env bash
set -euo pipefail
: "${FLOWSS_API_KEY:?Set FLOWSS_API_KEY}"
API="https://www.flowss.ai/api/v1/critique"
status=0
for f in docs/diagrams/*.mmd; do
body=$(jq -n --arg c "$(cat "$f")" '{engine:"mermaid", code:$c, gate:"sound"}')
res=$(curl -sS -X POST "$API" \
-H "Authorization: Bearer $FLOWSS_API_KEY" \
-H "Content-Type: application/json" \
-d "$body")
if [ "$(echo "$res" | jq -r '.pass // empty')" = "true" ]; then
echo "ok $f: $(echo "$res" | jq -r .readiness.level)"
else
echo "FAIL $f: $(echo "$res" | jq -r '.readiness.blocking[0] // .readiness.reason // .error')"
status=1
fi
done
exit $status
Each file is one deterministic call. With more than a few dozen diagrams, run this only on the files a pull request changed.
Diff every changed diagram in one call (Node.js 18 or later)
const res = await fetch("https://www.flowss.ai/api/v1/diff?format=markdown", {
method: "POST",
headers: {
Authorization: `Bearer ${process.env.FLOWSS_API_KEY}`,
"Content-Type": "application/json",
},
body: JSON.stringify({
files: [
{ path: "docs/arch.d2", engine: "d2", before: oldArch, after: newArch },
{ path: "db/schema.sql", engine: "sql", before: oldSchema, after: newSchema },
],
}),
});
if (!res.ok) throw new Error(`${res.status}: ${(await res.json()).error}`);
const { results, changed, total, markdown } = await res.json();
console.log(`${changed} of ${total} diagrams changed`);
console.log(markdown); // paste into a pull-request comment
Generate a diagram and save it as SVG (Python)
import os, requests
KEY = os.environ["FLOWSS_API_KEY"]
H = {"Authorization": f"Bearer {KEY}", "Content-Type": "application/json"}
gen = requests.post("https://www.flowss.ai/api/v1/diagram", headers=H, timeout=60,
json={"prompt": "Order-to-cash process with credit check", "engine": "mermaid"})
gen.raise_for_status()
d = gen.json()
print(d["title"], d["embed_url"])
# Rasterise on the server where the engine allows it (see the render API page).
img = requests.post("https://www.flowss.ai/api/render", headers=H, timeout=60,
json={"engine": d["engine"], "code": d["code"], "format": "svg"})
if img.ok:
open("diagram.svg", "wb").write(img.content)
else:
print("Render not available here:", img.json().get("error"))
Retrying safely
- Retry 502 and 503 responses with exponential backoff, and honour
Retry-Afterwhen it is present. - Do not retry 400, 401, 402, 404, 413 or 422 unchanged: they will fail the same way.
- Do not retry a 429 before midnight UTC; it will fail the same way until the allowance resets.
Tips
- Start every new integration with a
GETon the endpoint: it costs nothing and shows the exact body it accepts today. - Batch
generateanddiffcalls. A pull request touching 40 diagrams costs one call in batch mode and 40 otherwise. - Always send
sourceEnginetoconvert. Without it the built-in converters cannot run and every conversion uses AI. - Pin
directionandtitleongenerateso a committed artifact never changes for reasons other than its source. - Send
requesttocritiquewhen the diagram was made from a written brief: it is the only way the fidelity dimension is judged. - Name verified diagram nodes after the services, tables or endpoints they stand for, so
verifycan match them. - Keep one key per pipeline and check last used on your account page to find keys you can revoke.
Limits and known constraints
- The deterministic allowance is 60 calls a day per account on every plan, and it is shared with the render API, living-diagram drift checks and some Evidence actions. Upgrading does not raise it.
- Several endpoints count a request before reading its body, so a malformed request still spends one call.
generate,diffandverifyunderstand eight structural notations plus Weave boards, and read Mermaid flowcharts only. Other engines get a text-only comparison ondiffand are refused bygenerate(as anok: falseresult) andverify.verifymatches entities by normalised name; differently named nodes for the same thing are reported as drift. Extra relations drawn in the diagram are reported but do not failpass.detectandconvertare not counted against the 60-a-day deterministic allowance, but their AI path is limited by your points and your daily own-key ceiling.critiquecannot draw browser-rendered engines on the server, so for those the render check is reported as not run.detectsends at most the first 4,000 characters of a snippet to AI.- The
diagramendpoint returns anembed_urlthat encodes the whole source in the address. Very large diagrams make very long links; for those, save the source and use the embed script instead. - The API sends no CORS headers and is meant for servers, scripts and CI, not for browser code on other sites.
- Keys cannot be scoped to particular endpoints or projects and do not expire; revoke unused keys.
- A key saved to your account for the web app is not used by API calls; send
X-User-Api-Keywith each request instead.
Troubleshooting
| Symptom | Cause | Fix |
|---|---|---|
| 401 on every call | The key is missing, mistyped, revoked, or sent without the glyp_ prefix | Copy the key again from your secret store; if lost, create a new one |
/api/render answers 401 sign-in-required | No key was sent | Send Authorization: Bearer glyp_… (or X-Api-Key: glyp_…) |
/api/render answers 402 engine-plan | A Lab (glyphscript) render on the Free plan | Upgrade to Starter, or render a Studio engine |
402 points-exhausted on Free | Free includes no AI | Use the deterministic endpoints, or upgrade |
402 points-exhausted on Starter | Starter has no hosted points, and a key saved to your account is not used by API calls | Send X-User-Api-Key with your provider key on every request |
429 on diagram on Starter although you send your own key | Own-key diagram calls still count toward the 60-a-day diagram ceiling | Spread the work over days, or upgrade for a higher ceiling |
502 Generation failed — the upstream credentials were rejected. on diagram | Your provider refused the key in X-User-Api-Key, or the provider header names the wrong provider | Check the key and send the matching X-User-Ai-Provider |
| A revoked key is back in your key list | The list can show revoked keys after a reload | It no longer works. Revoke on it again answers Key not found or already revoked. |
| Create key shows an error | The key could not be created at that moment | Try again; nothing was created |
| 429 by mid-morning | Something else on your account is spending the same allowance: another pipeline, render calls or in-app drift checks | Batch calls, run checks only on changed files, and give each pipeline its own key so last used shows which ones run |
diff says structured: false | The engine has no structural reader, or a side failed to parse | Use one of the eight structural engines, or check the source parses in the Studio |
verify reports a service as missing and a node as stale that are the same thing | Their names do not match after normalisation | Rename the node to the source's name |
convert returns transpiler-lossy | AI was not available and the built-in converter could not carry everything | Read notes, or retry with points or your own key |
detect keeps answering low | The snippet is ambiguous and AI was not available | Send more of the source, or set the engine yourself |
| CORS error in the browser console | The API is not callable from browser code on another site | Call it from your server |
