Endpoints
Draw a figure
Turn an answer into a picture. Free, and only for answers we vouched for.
Draw a figure this API already vouched for.
Request
| token | The figure.token from an answer.min length 1 |
|---|---|
| format | svg or png. |
curl -X POST https://api.formalogic.io/v1/figures/render \
-H "Authorization: Bearer $FORMA_API_KEY" \
-H "Content-Type: application/json" \
-d '{"token":"…"}' \
--fail-with-body -o figure.svgimport os
import httpx
response = httpx.post(
"https://api.formalogic.io/v1/figures/render",
headers={"Authorization": f"Bearer {os.environ['FORMA_API_KEY']}"},
json={
"token": "…",
},
)
response.raise_for_status()
with open("figure.svg", "wb") as f:
f.write(response.content)import { writeFile } from 'node:fs/promises'
const response = await fetch('https://api.formalogic.io/v1/figures/render', {
method: 'POST',
headers: {
Authorization: `Bearer ${process.env.FORMA_API_KEY}`,
'Content-Type': 'application/json',
},
body: JSON.stringify({
"token": "…"
}),
})
await writeFile('figure.svg', Buffer.from(await response.arrayBuffer()))Response 200
The drawn figure: image/svg+xml, or image/png when you ask for png.
Errors
| 401KEY_MISSINGKEY_UNKNOWNKEY_REVOKEDKEY_EXPIRED | The key is missing, unknown, revoked or expired — see Authentication |
|---|---|
| 403ORG_DISABLED | The key is fine and its organisation is not active — see Authentication |
| 400SIGNATURE_INVALID | The token is malformed, altered or expired |
| 422VALIDATION_FAILED | The body is missing token, the format is unknown, the answer names data it does not carry, or the figure contains something Forma does not produce |
| 501UNSUPPORTED | Nothing draws this target |
| 503UPSTREAM_UNAVAILABLE | A figure could not be drawn because of a problem on our side, or drawing is briefly unavailable. The answer is unaffected; retry after a short wait |
Every code, and what to do about it, is on Errors.
The request is generated from the API's OpenAPI document; the response and errors are held to real responses and to the Errors page.
Some answers can be drawn. When one can, its response carries a figure block:
{
"status": "valid",
"result": { "mark": "bar", "data": { "values": [] } },
"figure": {
"drawable": true,
"target": "vega-lite",
"token": "v2.dmVnYS1saXRlADE3OTAx…"
}
}Send that token here and you get the picture back.
Why a token and not an id
Nothing is stored. The token carries the artifact — it is the answer plus a signature proving we produced it — so there is no record to look up and no copy of your output kept on our side. It expires an hour after the answer.
It also means you never have to rebuild anything. Pass the token through unchanged; reconstructing the source yourself would have to match the signed bytes exactly, and a single space in the wrong place is indistinguishable from a forgery.
Formats
format |
||
|---|---|---|
svg |
the default | vector — what a browser or a document wants |
png |
raster | what a model can look at; SVG is not something an agent can read |
The MCP show_figure tool defaults to png for that reason: its reader is a
model.
What draws
vega-lite, tikz, pictex, metapost and gnuplot. TeX-family figures come
back with their text as outlines, so the picture needs no fonts. Anything else
is a 501 naming the target.
What a figure may contain
A figure is not a document you author — it is output Forma returned, handed back to us. So the renderer checks that it is that: every command in it has to be one Forma's own output can contain. Anything else is refused by name rather than run.
That is stricter than it may sound, and deliberately so. TeX is a programming language, MetaPost can read files, and gnuplot will run a shell command given a backtick in a chart title. Rather than scan for dangerous constructs — which fails, because TeX can build any command out of characters — the renderer starts from the short list of things Forma's output contains and refuses the rest.
The practical consequence: figures Forma returned draw, and hand-written TeX
does not. If you want to typeset your own document, take the source from the
answer and run it yourself — the tikz output is a fragment that drops straight
into a tikzpicture environment.
Three states, not two
drawable answers a question with three honest outcomes, and the reason
says which:
drawable: true |
there is a picture, and the token draws it |
| no renderer | nothing can draw this target — some targets produce data, not pictures |
| not drawn, with a reason | the answer names data it does not carry |
That last one is deliberate. A chart whose data is
{"url": "sales.csv"} is pointing at a file we have never seen, so there is
nothing truthful to draw — and we do not go and fetch it. The same applies to a
chart that generates its own rows rather than carrying them. Put the numbers in
your intent and the chart carries them, and draws.
Free
Drawing costs no credits. You already paid for the answer; what you display it with should not change the price.