Natural-language Q&A over a US coordinate (LLM-backed)
API reference
POST /v1/ask
Natural-language Q&A over US coordinates with per-citation provenance.
POST
Natural-language Q&A over a US coordinate (LLM-backed)
When to use this
Use/v1/ask when the caller has a question phrased in natural language
(“is this in a flood zone?”, “what’s the wildfire risk here?”) and the
right answer depends on combining one or more fields with light
interpretation. The endpoint runs a two-model pipeline that picks fields
from the catalog, fetches them, and synthesizes the answer with citations.
Use /v1/fetch instead when the caller already
knows which fields they want, or when you are building a deterministic
workflow that should not pay LLM latency on every call.
How it works
- Planner (Claude Haiku 4.5) — receives the question, the
coordinate, and the field catalog rendered into the system prompt.
Returns a structured tool call naming the catalog fields it wants
fetched (capped at 15 per question — note that preset expansions
larger than the cap, e.g.
site_selectionordata_center_siting, are truncated to the first 15 fields of the preset). The planner system prompt is prompt-cached (catalog-sized), so steady-state input cost is ~90% below the uncached path. - Fetch (deterministic, parallel) — the orchestrator groups the
selected fields by layer and dispatches one fan-out per layer. Each
layer returns
Field_[T]wrappers carrying value + provenance. - Synthesizer (Claude Sonnet 4.6) — receives the question and the rendered values, returns a structured tool call with the prose answer and the list of fields it actually used.
- Citation extraction (deterministic) — walks
fields_used, groups them by source, and emits one citation per source.
Worked example: wildfire risk in rural Minnesota
Aitkin County, Minnesota — a rural parcel of open herbaceous cover and wetlands. A small-business insurer wants a wildfire underwriting read.wildfire_underwrite preset
(preset_expanded in the trace) — exactly its six fields. Its visible
reasoning weighed adding land_use_class and cdl_class for the
“fuel on the ground” angle and judged the preset’s signals sufficient.
Note the medium answer confidence: all six fields returned (so no
calibration downgrade fired), but the slope citation carries medium
source confidence and the answer itself flags the −0.49 five-year NDVI
decline as the open question — the synthesizer’s self-report is bounded
accordingly.
Confidence calibration
confidence is a three-bucket string: high, medium, low. The
synthesizer self-reports a bucket, then a deterministic calibration step
bounds its optimism:
The null downgrade is automatic: if more than 30% of the
planner-selected fields came back null, the response
confidence drops
one bucket (high → medium, medium → low) regardless of what the
synthesizer self-reported.
Latency and cost
/v1/ask is LLM-backed and much slower than /v1/fetch — it runs a serial
plan → fetch → synthesize pipeline. Set your client timeout to at least 120
seconds (or use streaming). A 30 s timeout will
intermittently abort otherwise-successful requests — and the request keeps
running, and billing, on the server after your client gives up.
- Steady-state warm path: ~6–15 s — single-field questions at the low end, multi-field synthesis at the high end. Planner 2–6 s, fetch fan-out sub-second when the layer cache is warm, synthesizer 3–8 s.
- Tail latency comes from the fetch stage, not the models. A question that selects a slow source lets that source’s own budget dominate: Earth Engine fields allow up to 60 s, road/building fields 30–35 s. One such field can push a single request well past 30 s while the models stay fast — expected, not a failure.
- Deadline: requests are bounded at 110 seconds end-to-end — past that
you get a structured
504 ask_timeout(carrying aRetry-Afterheader) instead of a hang. Per-stage timeouts inside the pipeline: planner 20 s, synthesizer 30 s, one SDK retry. - Cold start: the hosted deploy keeps machines running, but the first
requests after a deploy can be slower while geospatial sources warm in the
background (
/readyzreports warm state). Self-hosted instances pay this on every boot. - Cache savings: the planner system prompt is catalog-sized; cache reads land at a ~90% input-token discount. Per-question incremental cost is dominated by synthesizer output (~500 tokens at Sonnet rates).
Streaming responses
For latency-sensitive or interactive clients,POST /v1/ask/stream streams the
answer over Server-Sent Events
so you get first tokens in ~5–7 s (planner + fetch) instead of awaiting the
whole synthesis. It takes the same request body as /v1/ask and enforces the
same 110 s deadline and error codes.
Frames arrive as named SSE events:
ask_busy) surface as a normal HTTP status; once the 200
body has started, a later failure can only arrive as a terminal error frame.
Consume the final frame for the authoritative body — the delta stream is a
progressive preview of the same prose.
Including the trace
Pass"include_trace": true to get planner-internal diagnostics:
cache_read_input_tokens field is the load-bearing signal that the
planner system prompt cached correctly (11,767 tokens measured live
against the 0.5.0 catalog; the cached prompt scales with the catalog —
roughly 19 K tokens at 0.6.0, since the rendered field catalog dominates
the prompt).
First-ever calls show the same number under
cache_creation_input_tokens instead.
Errors
Beyond the common error codes,/v1/ask maps
LLM-upstream failures to structured codes — all with the
{"detail": {"error", "message", "retryable"}} shape:
Honor
retryable rather than the status code: a 502 ask_upstream_error
caused by an upstream 4xx will not succeed on retry. Retryable failures
(ask_busy, ask_timeout, and the upstream 429/502s) carry a
Retry-After response header in seconds — wait at least that long before
retrying, and prefer exponential backoff on repeats.
Partial-failure handling
/v1/ask is resilient to source failures. If 2 of 5 planner-selected
fields fail (timeout, source returned null, layer crashed), the
synthesizer still answers with the 3 that came back, the response
confidence drops one bucket (see the >30% rule above), and the answer
prose notes the gap (“…tree canopy was unavailable; assessment based on
LCMS class alone…”). Failed fields are not silently hidden: diff the
trace’s fields_requested against the top-level fields_used to see
exactly which planner-selected fields the answer could not use. For
field-level failure records (source, error, retryable), make the
same request via /v1/fetch, which returns a
structured partial_failures array.
Every /v1/ask response also carries a top-level data_gaps array: the
requested fields that resolved to no value, each with the fetch layer’s reason.
It is computed from the fetched result itself — not from the prose — so it is
authoritative missing-data you can read next to the answer without trusting the
wording or diffing fields_requested against fields_used:
data_gaps is [] when every requested field returned a value, and it appears
on both the buffered response and the streaming final frame.