REST API · v1
SatQuery AI API
One endpoint takes GeoTIFFs and a natural-language question and returns a grounded answer: task, evidence geometries, overlays, calibrated confidence and the full execution trace. The same contract powers the analysis console.
Base URL
http://localhost:8050
Auth
Authorization: Bearer <key> — optional on local deployments
OpenAPI
http://localhost:8050/docsQuickstart
Send one or two images as multipart form data. Two images are paired automatically — optical + SAR becomes cross-modal fusion, two dates become change analysis.
curl -X POST "http://localhost:8050/v1/analyze" \ -H "Authorization: Bearer $SATQUERY_KEY" \ -F "query=Use the optical and SAR images together to identify built-up and water-covered regions." \ -F "images=@MUM_S2L2A_20250106_T43QBB.tif" \ -F "images=@MUM_S1RTC_20250107_VVVH.tif" \ -F 'options={"return_overlays": true, "return_geojson": true}'
Playground
Try the contract against the bundled Sentinel test scenes. Files are fetched and validated in your browser exactly as the server would.
Endpoint
images (multipart)
query
Runs in your browser: the sample GeoTIFFs are fetched and validated for real, and answers come from the cached specialist outputs. Set NEXT_PUBLIC_API_URL to target a live server.
// Choose an endpoint and press “Send request”.
POST /v1/analyze
Validates the inputs, classifies the query, plans and runs the specialist tools, and returns the grounded answer. Content type: multipart/form-data.
| Field | Type | Required | Description |
|---|---|---|---|
| query | string | required | Natural-language question, 3–500 characters. |
| images | file[1..2] | required | GeoTIFF / TIFF. PNG / JPEG only for public benchmark samples (flagged in the response). |
| mode | enum | optional | auto (default) · single · cross_modal · bitemporal. auto infers the mode from sensors and dates. |
| options.return_overlays | boolean | optional | Include raster overlay URLs (class maps, change maps). Default true. |
| options.return_geojson | boolean | optional | Include evidence geometries as GeoJSON in EPSG:4326. Default true. |
| options.min_confidence | number | optional | 0–1. Answers below this are returned with status “abstained”. Default 0.6. |
| options.language | enum | optional | en (default) · hi |
Tool parameters cannot be passed freely — the controller sets them within the permitted ranges published in the tool registry.
POST /v1/validate
Runs only the geo-validator: per-file checks (format, CRS, bands, grid, acquisition time, no-data) and, for two files, pair checks (footprint IoU, co-registration offset, modality pairing, time gap). Returns the inferred mode.
curl -X POST "http://localhost:8050/v1/validate" -F "images=@HYD_S2L2A_20250107_T44QKE.tif" -F "images=@MUM_S1RTC_20250107_VVVH.tif" # → { "valid": false, "pair": { "status": "fail", "summary": "Pair rejected — the images do not describe the same area.", ... } }
GET /v1/tools
Lists every registered tool with its version, role, supported tasks and permitted parameter ranges. The same registry drives planning, so the trace can always be matched back to it.
Jobs & reports
Large scenes run asynchronously. Every completed analysis also exposes its reports.
| POST | /v1/jobs | Same body as /v1/analyze; returns { job_id, status: "queued" } immediately. |
| GET | /v1/jobs/{job_id} | queued · running (with the live trace so far) · done (full analysis) · failed. |
| GET | /v1/reports/{id}.pdf | PDF report: query, answer, confidence, evidence image, inputs and the execution trace. |
| GET | /v1/reports/{id}.geojson | Evidence geometries as a FeatureCollection (EPSG:4326). |
| GET | /v1/reports/{id}.json | Machine-readable audit record: inputs, compatibility, trace, models. |
| GET | /health | { status, models_loaded, device } — liveness and readiness. |
Legacy /analyze
The FastAPI server in src/api/main.py serves this endpoint today. It returns the answer and trace; the v1 fields above extend it with evidence, overlays and reports.
curl -X POST "http://localhost:8050/analyze" \ -F "query=Describe the land-cover and major objects visible in this image." \ -F "file1=@HYD_S2L2A_20250107_T44QKE.tif" # optional second image: -F "file2=@…" # → { "query": "…", "result": "…", "execution_trace": [ { "step": "Routing", "action": "…" }, … ] }
Response schema
Abridged example for a grounding query on the Hyderabad scene (coordinates elided).
{ "id": "req_7f3k2q9d", "object": "analysis", "created": "2026-09-26T10:41:07Z", "query": "Highlight the water body referred to in the query.", "input": { "mode": "single", "images": [ { "slot": "A", "name": "HYD_S2L2A_20250107_T44QKE.tif", "format": "GeoTIFF", "sensor": "Sentinel-2 MSI", "modality": "optical", "crs": "EPSG:32644", "size_px": [702, 786], "resolution_m": 10, "acquired": "2025-01-07T05:11:19Z" } ], "compatibility": null }, "task": "grounding", "task_label": "Text-guided region grounding", "status": "completed", "answer": "**Grounded: Hussain Sagar Lake** — the single large water body in the scene. Area 410.9 ha …", "confidence": 0.95, "confidence_parts": { "model_likelihood": 0.94, "cross_tool_agreement": 0.97, "input_quality": 0.99 }, "evidence": [ { "id": "hyd-lake", "type": "polygon", "label": "Hussain Sagar Lake", "confidence": 0.96, "geometry": { "type": "Polygon", "coordinates": [ [ [78.46717, 17.43529], [78.46726, 17.43515], /* … 39 more coordinates */ ] ] }, "pixel_bbox": [245.5, 333.2, 502.8, 622.8], "stats": { "area": "410.9 ha (4.11 km²)", "perimeter": "12.8 km" } } ], "overlays": [], "charts": ["hyd-spectral", "hyd-composition"], "execution_trace": [ { "step": 1, "stage": "validate", "tool": "geo-validator", "version": "1.2.0", "params": { "files": 1, "formats": "TIFF", "crs": "EPSG:32644" }, "output": "1/1 file valid · GeoTIFF EPSG:32644 6×uint16 (optical)", "duration_ms": 280, "status": "done" }, { "step": 2, "stage": "route", "tool": "agent-controller", "version": "1.1.0", "params": { "mode": "single", "modalities": "optical", "min_intent_confidence": 0.55 }, "output": "task = grounding (0.97) · target = “water body”", "duration_ms": 360, "status": "done" }, { "step": 4, "stage": "execute", "tool": "rs-grounder", "version": "0.9.0", "params": { "text": "the water body", "box_threshold": 0.35, "top_k": 3, "refine": "spectral" }, "output": "2 candidate regions · top score 0.93", "duration_ms": 820, "status": "done" } ], "models": ["geo-validator@1.2.0", "agent-controller@1.1.0", "rs-grounder@0.9.0", "spectral-indices@1.0.0", "satquery-vlm@1.2.0", "report-builder@1.0.0"], "reports": { "pdf": "/v1/reports/7f3k2q9d.pdf", "geojson": "/v1/reports/7f3k2q9d.geojson", "json": "/v1/reports/7f3k2q9d.json" } }
Tool registry
Each tool declares the only parameters the controller may set, with their permitted range and default.
| Tool | Role | Method | Permitted parameters |
|---|---|---|---|
geo-validator v1.2.0 | guardrail | Input validator GDAL/rasterio metadata checks: format, CRS, bands, grid, acquisition time, footprint IoU, co-registration | min_overlap_iou ∈ 0.50 – 1.00 (default 0.95) max_crossmodal_gap_h ∈ 1 – 288 (default 72) |
agent-controller v1.1.0 | controller | Agent controller LangGraph state machine: intent classification (keyword prior + LLM, JSON schema) → tool DAG planning → evidence aggregation and confidence calibration | min_intent_confidence ∈ 0.30 – 0.90 (default 0.55) abstain_below ∈ 0.40 – 0.80 (default 0.60) |
satquery-vlm v1.2.0 | vlm | SatQuery-VLM Qwen2-VL-2B-Instruct + remote-sensing LoRA (r = 16, α = 32, q/k/v/o) adapted on BigEarthNet.txt | temperature ∈ 0.0 – 0.7 (default 0.2) max_new_tokens ∈ 64 – 512 (default 320) image_size ∈ 448 | 672 (default 672) |
rs-grounder v0.9.0 | specialist | Text-guided grounder Open-vocabulary detector + mask refinement, adapted on VRSBench grounding | box_threshold ∈ 0.20 – 0.60 (default 0.35) top_k ∈ 1 – 10 (default 3) refine ∈ spectral | sar | none (default spectral) |
spectral-indices v1.0.0 | specialist | Spectral index engine NDVI · NDWI · MNDWI · NDBI + rule-based land cover, connected-component sieve | veg_ndvi ∈ 0.20 – 0.50 (default 0.33) water_mndwi ∈ −0.10 – 0.30 (default 0.00) sieve_px ∈ 0 – 64 (default 12) |
sar-segmenter v1.1.0 | specialist | SAR segmenter γ⁰ VV/VH (dB) thresholding with Otsu water split + double-bounce built-up test | water_threshold_db ∈ otsu | −25 – −10 (default otsu) builtup_threshold_db ∈ −8 – 0 (default −4) |
sar-point-detector v0.7.0 | specialist | SAR point-target detector CFAR-style bright-target test inside the water mask (vessels, buoys) | min_vv_db ∈ −5 – +10 (default −2) max_target_px ∈ 10 – 100 (default 60) |
optsar-fusion v1.0.0 | fusion | Optical–SAR fusion Evidence-level late fusion of optical indices and SAR backscatter + disagreement analysis | ruleset ∈ rs-fusion-v1 (default rs-fusion-v1) min_agreement ∈ 0.0 – 1.0 (default 0.5) |
change-detector v1.0.0 | specialist | Change detector Post-classification comparison + change-vector analysis (NDVI, MNDWI, NDBI, brightness) | cva_threshold ∈ 0.05 – 0.30 (default 0.12) min_region_px ∈ 10 – 500 (default 25) |
cd-vqa v0.8.0 | vlm | Change-VQA head SatQuery-VLM prompted with the change map + transition statistics (CDVQA-style) | temperature ∈ 0.0 – 0.5 (default 0.1) max_new_tokens ∈ 64 – 384 (default 256) |
timeseries-profiler v0.9.0 | specialist | Time-series profiler Per-epoch land-cover shares inside a footprint (same season, same MGRS tile) | season ∈ any month window (default Jan–Feb) max_epochs ∈ 2 – 12 (default 10) |
report-builder v1.0.0 | output | Report builder PDF report · GeoJSON evidence · JSON execution trace | formats ∈ pdf, geojson, json (default pdf, geojson, json) |
Task routing
How the controller maps a classified task onto a tool DAG. Try any example in the console.
| Task | Tool DAG | Example query |
|---|---|---|
captioning Captioning / scene description | spectral-indices → rs-grounder → satquery-vlm | “Describe the land-cover and major objects visible in this image.” |
vqa Visual question answering | spectral-indices → satquery-vlm (+ rs-grounder for “where”) | “Is there an airport in this image? Where is the runway?” |
grounding Text-guided region grounding | rs-grounder → spectral-indices / sar-segmenter (mask refine) → satquery-vlm | “Highlight the water body referred to in the query.” |
fusion_extraction Optical–SAR joint extraction | spectral-indices + sar-segmenter → optsar-fusion → satquery-vlm | “Use the optical and SAR images together to identify built-up and water-covered regions.” |
fusion_vqa Cross-modal VQA | spectral-indices + sar-segmenter (+ sar-point-detector) → optsar-fusion | “Where do the optical and SAR images disagree, and why?” |
change_description Change description | spectral-indices ×2 → change-detector → cd-vqa | “What changed between these two dates, and where did the change occur?” |
change_vqa Change-based VQA | spectral-indices ×2 → change-detector → cd-vqa | “Has the built-up area increased, decreased, or remained unchanged?” |
timeseries Temporal trend analysis | timeseries-profiler → cd-vqa | “Show the year-by-year trend of the construction.” |
Errors
Errors use a stable code. Validation failures stop the pipeline before any model runs and still return the partial trace.
| HTTP | Code | When |
|---|---|---|
| 400 | INVALID_FORMAT | Unsupported file type, or PNG/JPEG outside a declared benchmark sample |
| 409 | INCOMPATIBLE_PAIR | Footprints do not overlap, co-registration failed, or the cross-modal time gap is too long |
| 413 | PAYLOAD_TOO_LARGE | More than 2 images or a file above the configured size limit — use async jobs |
| 422 | UNSUPPORTED_QUERY | Intent confidence below min_intent_confidence, or the task needs a different input configuration |
| 422 | PARAM_OUT_OF_RANGE | An options value is outside the tool's permitted range (see the registry) |
| 429 | RATE_LIMITED | Too many requests for this key |
| 500 | MODEL_ERROR | A specialist failed; the partial trace is returned in error.trace |
{
"status": "rejected",
"error": { "code": "INCOMPATIBLE_PAIR", "message": "No overlap — footprints are 620 km apart (IoU 0.00)" },
"execution_trace": [
{ "step": 1, "stage": "validate", "tool": "geo-validator", "status": "failed" },
{ "step": 2, "stage": "route", "tool": "agent-controller", "output": "task = rejected · no specialist tools executed" }
]
}See the guardrail live in the mismatched-pair scene.