# 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 — /retrieve returns the ranked claims themselves. Both carry the token-ROI meter.

### POST /ask — Ask 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.

Auth: decisions:read
Quota: Counts as one MCP query (the ask side of the daily wallet).

Request body:
- `query` (string, required) — The natural-language question about your decisions.
- `workspace_ids` (string[]) — Optional workspace UUIDs to focus the answer. Omitted, the answer draws on every workspace you can see.

Response:
- `scope` (string) — The workspaces the answer drew on.
- `answer` (string) — The synthesized answer, with inline [n] citation markers.
- `sources` (object[]) — One entry per citation, ordered so sources[i].n = i+1. Always present — empty when the answer is an honest abstention.
- `sources[].n` (integer) — The citation number the answer refers to.
- `sources[].ref` (string) — The source ref, e.g. an ADR number or PR.
- `sources[].type` (string) — The claim type: chosen, rejected, constraint, or consequence.
- `sources[].text` (string) — The claim — summarized from the record, never a verbatim quote.
- `sources[].status` (string) — The decision status, e.g. accepted or superseded.
- `sources[].workspace` (string) — Which workspace the claim came from.
- `sources[].locator` (string · optional) — A finer pointer into the source, when one exists.
- `sources[].url` (string · optional) — The source document URL, when known.
- `sources[].edges` (object[] · optional) — Typed edges off the cited decision: { type, ref } — supersedes, superseded_by, depends_on, related_to.
- `sources[].rejected_alternatives` (string[] · optional) — The alternatives the cited decision ruled out.
- `sources[].relevance` (number · optional) — Relevance to the query — relevance, not confidence. Measured per call.
- `sources[].decision_id` (string · 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_tokens` (integer) — Tokens in the claims actually served.
- `usage.source_decisions` (integer) — How many source decisions those claims cite.
- `usage.source_tokens` (integer) — Tokens in the full source documents behind them — what you did not have to load.
- `usage.tokens_saved` (integer) — source_tokens minus atoms_tokens. Conservative: only sources lema can size are counted.
- `usage.compression_ratio` (number) — source_tokens over atoms_tokens, measured per call.
- `synthesis_tokens_in / synthesis_tokens_out` (integer) — The synthesis cost of this call — the other side of the meter.

Errors:
- 400 — query missing, malformed workspace UUID, or an unknown JSON field.
- 403 — a requested workspace exists but is not yours to see.
- 429 — the daily MCP-query wallet is spent — the structured limit signal below.
- 501 — this 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.

Example:

```
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 — Retrieve 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.

Auth: decisions:read
Quota: Counts as one MCP query (the retrieve side of the daily wallet).

Request body:
- `query` (string, required) — What to find.
- `workspace_ids` (string[]) — Optional workspace UUIDs to search. Omitted, every workspace you can see.
- `k` (integer) — Maximum claims to return. Default 8; values outside 1–50 fall back to the default.

Response:
- `scope` (string) — The workspaces searched.
- `atoms` (object[]) — The ranked claims. Each carries type, ref, text (summarized, never quoted), and — when present — locator, status, workspace, edges, rejected_alternatives.
- `usage.atoms_tokens` (integer) — Tokens in the claims actually served.
- `usage.source_decisions` (integer) — How many source decisions those claims cite.
- `usage.source_tokens` (integer) — Tokens in the full source documents behind them — what you did not have to load.
- `usage.tokens_saved` (integer) — source_tokens minus atoms_tokens. Conservative: only sources lema can size are counted.
- `usage.compression_ratio` (number) — source_tokens over atoms_tokens, measured per call.

Errors:
- 400 — query missing, malformed workspace UUID, or an unknown JSON field.
- 403 — a requested workspace exists but is not yours to see.
- 429 — the daily MCP-query wallet is spent.
- 501 — this 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.

Example:

```
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": …
  }
}
```
