lema · docs · api
Ask & retrieve
Two ways to query the graph: /ask returns one synthesized answer whose every sentence is grounded in a numbered, cited claim — /retrievereturns the ranked claims themselves, for callers assembling their own context. Both carry the token-ROI meter: what was served against what you didn’t have to load.
POST
/ask
/askAsk your graph
One synthesized, cited answer over your decision graph. Every [n] marker in the answer maps to sources[n-1] — the claim it was grounded in, with its source ref and stable decision id. When nothing recorded answers the question, the answer says so plainly and sources is empty — the meter reads zero rather than dressing up a guess.
Request body
querystringrequired- The natural-language question about your decisions.
workspace_idsstring[]- Optional workspace UUIDs to focus the answer. Omitted, the answer draws on every workspace you can see.
Response
scopestring- The workspaces the answer drew on.
answerstring- The synthesized answer, with inline [n] citation markers.
sourcesobject[]- One entry per citation, ordered so sources[i].n = i+1. Always present — empty when the answer is an honest abstention.
sources[].ninteger- The citation number the answer refers to.
sources[].refstring- The source ref, e.g. an ADR number or PR.
sources[].typestring- The claim type: chosen, rejected, constraint, or consequence.
sources[].textstring- The claim — summarized from the record, never a verbatim quote.
sources[].statusstring- The decision status, e.g. accepted or superseded.
sources[].workspacestring- Which workspace the claim came from.
sources[].locatorstring · optional- A finer pointer into the source, when one exists.
sources[].urlstring · optional- The source document URL, when known.
sources[].edgesobject[] · optional- Typed edges off the cited decision: { type, ref } — supersedes, superseded_by, depends_on, related_to.
sources[].rejected_alternativesstring[] · optional- The alternatives the cited decision ruled out.
sources[].relevancenumber · optional- Relevance to the query — relevance, not confidence. Measured per call.
sources[].decision_idstring · optional- The stable decision UUID. On this authed surface it is the key you keep — decision_url is never set over a private corpus.
usage.atoms_tokensinteger- Tokens in the claims actually served.
usage.source_decisionsinteger- How many source decisions those claims cite.
usage.source_tokensinteger- Tokens in the full source documents behind them — what you did not have to load.
usage.tokens_savedinteger- source_tokens minus atoms_tokens. Conservative: only sources lema can size are counted.
usage.compression_rationumber- source_tokens over atoms_tokens, measured per call.
synthesis_tokens_in / synthesis_tokens_outinteger- The synthesis cost of this call — the other side of the meter.
Errors
400query missing, malformed workspace UUID, or an unknown JSON field.403a requested workspace exists but is not yours to see.429the daily MCP-query wallet is spent — the structured limit signal below.501this deployment is not configured to answer.
Honest abstention is first-class: when nothing recorded clears the relevance floor, the answer states the gap, sources is [], and the usage meter is zero — the citations never contradict the answer.
A caller with no workspaces yet gets a 200 with scope "no workspaces" and an explanatory answer, not an error.
$ curl https://api.lema.sh/ask \
-H "Authorization: Bearer $LEMA_API_TOKEN" \
-H "Content-Type: application/json" \
-d '{"query": "why is the ledger service split from the monolith?"}'{
"scope": "<the workspaces answered over>",
"answer": "Split after the double-write incident: audit writes
require serializable transactions [1]. Re-merging was
argued and ruled out [2].",
"sources": [
{
"n": 1,
"ref": "ADR-0019",
"type": "chosen",
"text": "<summarized from the record — never a verbatim quote>",
"status": "accepted",
"workspace": "payments-api",
"decision_id": "<stable uuid>"
}
],
"usage": {
"atoms_tokens": …, "source_decisions": …, "source_tokens": …,
"tokens_saved": …, "compression_ratio": …
},
"synthesis_tokens_in": …,
"synthesis_tokens_out": …
}POST
/retrieve
/retrieveRetrieve raw claims
/ask minus the synthesis: the ranked, typed claims themselves plus the token-ROI meter, for callers that build their own context. No relevance floor is applied — you get the top-k as ranked and weigh relevance yourself.
Request body
querystringrequired- What to find.
workspace_idsstring[]- Optional workspace UUIDs to search. Omitted, every workspace you can see.
kinteger- Maximum claims to return. Default 8; values outside 1–50 fall back to the default.
Response
scopestring- The workspaces searched.
atomsobject[]- The ranked claims. Each carries type, ref, text (summarized, never quoted), and — when present — locator, status, workspace, edges, rejected_alternatives.
usage.atoms_tokensinteger- Tokens in the claims actually served.
usage.source_decisionsinteger- How many source decisions those claims cite.
usage.source_tokensinteger- Tokens in the full source documents behind them — what you did not have to load.
usage.tokens_savedinteger- source_tokens minus atoms_tokens. Conservative: only sources lema can size are counted.
usage.compression_rationumber- source_tokens over atoms_tokens, measured per call.
Errors
400query missing, malformed workspace UUID, or an unknown JSON field.403a requested workspace exists but is not yours to see.429the daily MCP-query wallet is spent.501this deployment is not configured to answer.
The ROI baseline is conservative: it sizes only the source documents it can resolve (ADR-form refs), so tokens_saved understates rather than inflates.
$ curl https://api.lema.sh/retrieve \
-H "Authorization: Bearer $LEMA_API_TOKEN" \
-H "Content-Type: application/json" \
-d '{"query": "audit log constraints", "k": 8}'{
"scope": "<the workspaces searched>",
"atoms": [
{
"type": "constraint",
"ref": "ADR-0019",
"text": "<summarized from the record — never a verbatim quote>",
"status": "accepted",
"workspace": "payments-api"
}
],
"usage": {
"atoms_tokens": …, "source_decisions": …, "source_tokens": …,
"tokens_saved": …, "compression_ratio": …
}
}