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

Quickstart

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.

POST/v1/analyze

// 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.

FieldTypeRequiredDescription
querystringrequiredNatural-language question, 3–500 characters.
imagesfile[1..2]requiredGeoTIFF / TIFF. PNG / JPEG only for public benchmark samples (flagged in the response).
modeenumoptionalauto (default) · single · cross_modal · bitemporal. auto infers the mode from sensors and dates.
options.return_overlaysbooleanoptionalInclude raster overlay URLs (class maps, change maps). Default true.
options.return_geojsonbooleanoptionalInclude evidence geometries as GeoJSON in EPSG:4326. Default true.
options.min_confidencenumberoptional0–1. Answers below this are returned with status “abstained”. Default 0.6.
options.languageenumoptionalen (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/jobsSame 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}.pdfPDF report: query, answer, confidence, evidence image, inputs and the execution trace.
GET/v1/reports/{id}.geojsonEvidence geometries as a FeatureCollection (EPSG:4326).
GET/v1/reports/{id}.jsonMachine-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.

ToolRoleMethodPermitted 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.

TaskTool DAGExample 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.

HTTPCodeWhen
400INVALID_FORMATUnsupported file type, or PNG/JPEG outside a declared benchmark sample
409INCOMPATIBLE_PAIRFootprints do not overlap, co-registration failed, or the cross-modal time gap is too long
413PAYLOAD_TOO_LARGEMore than 2 images or a file above the configured size limit — use async jobs
422UNSUPPORTED_QUERYIntent confidence below min_intent_confidence, or the task needs a different input configuration
422PARAM_OUT_OF_RANGEAn options value is outside the tool's permitted range (see the registry)
429RATE_LIMITEDToo many requests for this key
500MODEL_ERRORA 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.