Skip to main content
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

  1. 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_selection or data_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.
  2. 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.
  3. 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.
  4. 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.
The planner here expanded the 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 (highmedium, mediumlow) 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 a Retry-After header) 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 (/readyz reports 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:
Failures before the first byte (planner error, connection error, deadline, or an at-capacity 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:
The 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.

Body

application/json
lat
number
required
lng
number
required
question
string
required
Required string length: 1 - 2000
include_trace
boolean
default:false

Response

Successful Response