Endpoints
Ask an oracle
The one call that costs credits, and the one that returns a verdict.
Ask an oracle for an artifact, and get back a verdict on it.
Request
| name | An oracle name from GET /v1/oracles. |
|---|---|
| intent | What you want, in plain language. Not a template and not a specification — describe the thing, not how to build it. min length 1 · max length 4000 |
| format | One of the oracle's formats. Omit it for the oracle's default_format. A format the oracle cannot produce is refused with the ones it can. |
| allow_generated_data | Charts only. false (the default): a chart is drawn only from values the request gives, and a request without them is refused with 422 DATA_REQUIRED, unbilled. true: illustrative values are written when the request gives none, and the answer says "data": "generated". |
| views | Views to return beside a valid answer, by name, from the format's views in GET /v1/oracles (for example summary). Each comes back in views as text built from result alone; result is the same with or without them. Free. A view the format does not offer is refused with the ones it does, before anything runs. |
curl -X POST https://api.formalogic.io/v1/oracles/chart/ask \
-H "Authorization: Bearer $FORMA_API_KEY" \
-H "Content-Type: application/json" \
-d '{"intent":"grouped bar chart of quarterly sales, Q1-Q4: North 120, 135, 150, 160; South 90, 110, 105, 130"}'import os
import httpx
response = httpx.post(
"https://api.formalogic.io/v1/oracles/chart/ask",
headers={"Authorization": f"Bearer {os.environ['FORMA_API_KEY']}"},
json={
"intent": "grouped bar chart of quarterly sales, Q1-Q4: North 120, 135, 150, 160; South 90, 110, 105, 130",
},
timeout=120,
)
print(response.json())const response = await fetch('https://api.formalogic.io/v1/oracles/chart/ask', {
method: 'POST',
headers: {
Authorization: `Bearer ${process.env.FORMA_API_KEY}`,
'Content-Type': 'application/json',
},
body: JSON.stringify({
"intent": "grouped bar chart of quarterly sales, Q1-Q4: North 120, 135, 150, 160; South 90, 110, 105, 130"
}),
})
console.log(await response.json())Response 200
The verdict. status is valid (with result and a verified_by citation), invalid (the check refused it — billable, because the verdict is the product), or error (ours, unbilled, retryable).
| oracle | Echoed back |
|---|---|
| format | The format actually produced |
| status | valid, invalid or error |
| credits | What this call actually cost |
| request_id | The same id as the x-request-id header. Quote it in feedback or to support |
| result | The artifact, in format |
| verified_by | The version of the rules that accepted it |
| figure | Whether the answer can be drawn, and the token to draw it — see Draw a figure |
| figure.drawable | Whether the token draws a picture |
| figure.target | What would draw it, such as vega-lite or tikz |
| figure.token | Send it to POST /v1/figures/render. Valid for an hour |
| figure.reason | Why it cannot be drawn |
| intent | Whether the figure shows what was asked: passed (boolean), faithful (0–1) and problems (strings). vector-graphics only, and present only when that check ran |
| data | supplied: every value came from the request. generated: Forma wrote illustrative values, which you allowed with allow_generated_data |
| reason | unsupported_feature, chart_type_mismatch or no_valid_answer; detail says the same in a sentence |
| detail | Plain-language explanation |
| retryable | Always true when present |
| views | Each view asked for, by name, as text built from result alone; null if it could not be built. result is the same with or without it |
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 |
| 404NOT_FOUND | No such oracle, or no organisation for this key |
| 422VALIDATION_FAILED | The body is missing intent, or it is empty or over 4000 characters; or a format this oracle cannot produce (context.available lists the ones it can). context.parameter names the field |
| 422DATA_REQUIRED | A chart request does not give the values to draw, and allow_generated_data is not true. Not billed |
| 402QUOTA_EXCEEDED | Not enough credits for the call; see Limits and credits |
| 200 | status: "error" — the oracle could not answer. Not billed; retry |
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.
Example response
{
"oracle": "chart",
"format": "vega-lite",
"status": "valid",
"credits": 1,
"request_id": "req_01K…",
"result": { "$schema": "https://vega.github.io/schema/vega-lite/v6.json", "mark": "bar" },
"verified_by": "chart@6baf09f0",
"figure": { "drawable": true, "target": "vega-lite", "token": "v2.dmVnYS1saXRl…" },
"data": "supplied"
}status is a verdict, not a health check — all three arrive with HTTP 200.
See Verdicts for which are billed and which are worth retrying.
Notes
A chart is drawn from the values you give. Forma does not fetch or look up
data. A chart request with no values in it is refused with 422 DATA_REQUIRED
before anything runs; one whose chart would need values the request does not
contain is refused the same way. Neither is billed. Send
"allow_generated_data": true to accept illustrative values instead. Every valid
chart says which it is in data: supplied or generated. A chart with no
values at all is refused even with the flag. vector-graphics and coverage-policy ignore the flag.
Ask for a view to get the answer as text as well. Send "views": ["summary"]
to an oracle format that lists summary under views in
List oracles, and a valid answer carries views.summary: the
answer written out as plain text, built from result and nothing else. result
is the same with or without it, and it costs nothing more. A view the format
does not offer is refused with 422 before anything runs, naming the ones it
does; nothing is billed.
valid is about the answer, not the world. It means the output passed its
format's checks. It does not mean the numbers are true; data says where they
came from.
A chart of a different kind is not an answer. Ask for a candlestick and get a
line, and the verdict is invalid with reason: "chart_type_mismatch". Billed
like any invalid. Try another format: not every format can draw every kind.
format is read from the body and from nowhere else. The API never consults
an environment variable, so a request sent by curl and the same request sent by
the MCP tools mean the same thing.
Allow 120 seconds. Reaching a verdict can take that long. No streaming, no polling endpoint.
One ask is one billable unit, however long it took.
There is no idempotency key. Two identical requests are two asks and two charges, and because the same intent can return a different artifact they may differ — see Determinism. If a retry must not double-charge, store the first verdict and check for it before asking again.