Off-Nadir Delta
Developer reference

API & MCP

Query geolocated world event signals and the Daily World Brief programmatically — over a REST API and an MCP server. Usage is metered on your token balance.

Overview

The API and MCP server are available on every plan, including Free. Every endpoint lives under https://offnadir-delta.com/api/v1 and authenticates with an API key. Requests are billed to your own token balance — the same wallet used across the app.

Issue and manage keys from Account → Developer API. No plan yet? See pricing.

Quickstart

Fetch the free Daily World Brief to confirm your key works, then query signals:

# 1) Free — verify your key
curl -H "Authorization: Bearer ond_..." "https://offnadir-delta.com/api/v1/brief"

# 2) Query signals (3 token/page)
curl -H "Authorization: Bearer ond_..." \
  "https://offnadir-delta.com/api/v1/signals?days=1&categories=armed_conflict&limit=50"

Authentication

Pass your key as a bearer token (or the X-API-Key header). Keys are shown once at creation — store them securely and never expose them client-side.

Authorization: Bearer ond_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx

Errors

Errors return a JSON body with a stable machine-readable code, a human message, and a requestId for support.

{
  "error": "date must be YYYY-MM-DD",
  "code": "INVALID_PARAMETERS",
  "requestId": "req_1783..."
}
401Missing or invalid key
403Your plan does not include this endpoint
402Insufficient tokens for a metered call
400Invalid parameters (see the message)
404Resource not found
429Rate limit exceeded

Rate limits

Successful responses include X-RateLimit-Limit, X-RateLimit-Remaining, and X-RateLimit-Reset. When exceeded, the API returns 429 with a Retry-After header — back off and retry after the indicated delay.

Pagination

/signals is cursor-paginated. Each response carries meta.next_cursor and meta.has_more; pass the cursor back to fetch the next page. Each page is billed as one query.

# follow the cursor until has_more is false
curl -H "Authorization: Bearer ond_..." \
  "https://offnadir-delta.com/api/v1/signals?limit=100&cursor=MTAw"

Endpoints

Grouped by resource. Every endpoint lives under /api/v1 and is billed to your token balance.

Signals

Query geolocated event signals, aggregate trends, and geographic hotspots.

GET/api/v1/signals3 tok / page

Geolocated world event signals — geopolitical, security, disaster, and infrastructure events distilled from global news media, enriched with AI-verified geolocation, severity/GEOINT scores, and satellite-collection recommendations. Returns up to 500 rows per page (cursor-paginated).

Parameters

ParameterTypeDescription
bboxstringBounding box "minLon,minLat,maxLon,maxLat" (WGS84). Omit for worldwide.
datestring (date)Window end date YYYY-MM-DD (UTC). Defaults to today.
daysinteger 1–30Window length in days ending on `date`.
categoriesstringComma-separated category filter. Omit for all.
marketsstringComma-separated market-impact filter (oil, natural_gas, grain, shipping, defense, metals, semiconductors, fx, equities): only signals AI-tagged as exposing these markets via a direct physical/supply channel. Informational only — not investment advice. Omit for all.
limitinteger 1–500Max rows per page.
cursorstringOpaque pagination cursor from a previous response's meta.next_cursor.
min_severitynumber 0–10Keep only signals with severity_score >= this (0-10).
escalatingbooleanSet true to keep only signals whose escalation_trend is "escalating".
sortstringResult ordering. Omit for the feed default ranking. "geoint" ranks by GEOINT spatial-collection value so imageable, high-value events rise and non-observable news noise sinks.
updated_sincestring (ISO 8601)Differential fetch: only signals (re)enriched at/after this ISO 8601 timestamp. Ignores the date window; response signals carry last_updated_at. Use for incremental sync.
created_sincestring (ISO 8601)Differential fetch: only signals first enriched at/after this ISO 8601 timestamp.
observabilitystringKeep only signals with this satellite observability — is a physical mark imageable at all (intelligence.satellite_observability).
observability_statusstring3-state observability filter (observable / not_observable / insufficient_detail). Exposes the insufficient_detail bucket that the binary observability filter excludes from both sides.
open_datastringKeep only signals with this open-data sufficiency — free imagery enough vs commercial tasking recommended (intelligence.open_data_sufficiency).
min_information_gainnumber 0–1Keep only signals whose intelligence.expected_information_gain >= this (0-1).
taskable_onlybooleanKeep only signals whose coordinate is search_ready — GEO-READY ONLY: drops country centroids, ADM1 mismatches, reporting-dateline fallbacks and unresolved fixes (intelligence.geo_validation.search_ready). Does NOT imply observable or quality-passed; for automated imagery tasking use collection_ready_only (or combine with observability=observable).
collection_ready_onlybooleanSTRICT tasking-candidate filter: search_ready AND observability=observable AND quality.status!=failed AND a concrete collection plan (rs_target + rs_reason present) AND an event coordinate. The safe input set for automated imagery tasking.
Request
curl -H "Authorization: Bearer ond_..." \
  "https://offnadir-delta.com/api/v1/signals?days=1&categories=armed_conflict&limit=2"
Response
{
  "meta": {
    "start_date": "2026-07-08",
    "end_date": "2026-07-08",
    "bbox": [-180, -90, 180, 90],
    "categories": ["armed_conflict"],
    "count": 2,
    "coverage": {
      "bbox_area_share": 1,
      "worldwide": true,
      "global_available": null,
      "global_capped": false,
      "global_basis": "not_probed",
      "hints": []
    },
    "next_cursor": "Mg",
    "has_more": true,
    "tokens": { "charged": 3, "remaining": 9994 }
  },
  "signals": [
    {
      "id": 1312473707,
      "event_date": "2026-07-08",
      "category": "armed_conflict",
      "event_type": "…",
      "title": "…",
      "summary": "…",
      "location": "Kyiv, Ukraine",
      "country_code": "UA",
      "lat": 50.45, "lng": 30.52,
      "geo_verified": true,
      "severity_score": 7.8,
      "geoint_score": 6.4,
      "escalation_trend": "escalating",
      "cross_border_risk": true,
      "infrastructure_target": "energy",
      "goldstein_scale": -8,
      "avg_tone": -4.2,
      "actors": { "actor1": "…", "actor2": "…" },
      "tags": ["strike", "energy"],
      "num_mentions": 42,
      "num_sources": 11,
      "source_url": "https://…",
      "market_impact": ["oil"],
      "market_note": "…",
      "last_updated_at": "2026-07-08T06:12:00Z",
      "collection": {
        "rs_level": "GSD<1m", "rs_sensor": "sentinel-1-sar",
        "rs_target": "…", "rs_reason": "…",
        "rs_niirs": "…", "rs_analysis": "…"
      },
      "intelligence": {
        "satellite_observability": "observable",
        "open_data_sufficiency": "commercial-recommended",
        "expected_information_gain": 0.72
      }
    }
  ]
}
GET/api/v1/signals/{id}1 token

Re-read one signal you already hold an id for — cheaper than re-running the query that found it, and the way to check whether its verification or observability has moved since. A cluster member id resolves to its cluster.

Parameters

ParameterTypeDescription
idinteger (required)The signal id (global_event_id) from a /api/v1/signals result.
Request
curl -H "Authorization: Bearer ond_..." "https://offnadir-delta.com/api/v1/signals/1312473707"
Response
{
  "signal": { "id": 1312473707, "category": "armed_conflict", "severity": 7.4,
              "verification_status": "independently_confirmed", "independent_source_count": 4 },
  "meta": { "tokens": { "charged": 1 } }
}
GET/api/v1/signals/stats1 tok / query

Aggregate statistics over the signal corpus — total event count plus a per-category and per-day (trend) breakdown for a bounding box and date window. Roll-ups, not rows: cheaper than /signals.

Parameters

ParameterTypeDescription
bboxstringBounding box "minLon,minLat,maxLon,maxLat" (WGS84). Omit for worldwide.
datestring (date)Window end date YYYY-MM-DD (UTC). Defaults to today.
daysinteger 1–30Window length in days ending on `date`.
categoriesstringComma-separated category filter. Omit for all.
Request
curl -H "Authorization: Bearer ond_..." \
  "https://offnadir-delta.com/api/v1/signals/stats?days=7&categories=armed_conflict"
Response
{
  "meta": {
    "start_date": "2026-07-05", "end_date": "2026-07-11",
    "bbox": [-180, -90, 180, 90], "categories": ["armed_conflict"],
    "total": 8241,
    "tokens": { "charged": 1, "remaining": 9993 }
  },
  "stats": {
    "by_category": [{ "category": "armed_conflict", "count": 8241 }],
    "by_day": [{ "date": "2026-07-05", "count": 1180 }]
  }
}
GET/api/v1/signals/hotspots1 tok / query

Geographic hotspots — signal density grid-binned into cells and ranked by event count, each with peak severity and the categories present. Answers “where is activity concentrating right now?”

Parameters

ParameterTypeDescription
bboxstringBounding box "minLon,minLat,maxLon,maxLat" (WGS84). Omit for worldwide.
datestring (date)Window end date YYYY-MM-DD (UTC). Defaults to today.
daysinteger 1–30Window length in days ending on `date`.
categoriesstringComma-separated category filter. Omit for all.
precisionnumber 0.1–5Grid cell size in decimal degrees.
min_severitynumber 0–10Keep only points with severity_score >= this (0-10).
limitinteger 1–500Max source points sampled before grid-binning — not the number of cells returned. Lower values fragment clusters; leave at the default for a representative density map.
Request
curl -H "Authorization: Bearer ond_..." \
  "https://offnadir-delta.com/api/v1/signals/hotspots?days=3&precision=1&min_severity=6"
Response
{
  "meta": {
    "start_date": "2026-07-09", "end_date": "2026-07-11",
    "bbox": [-180, -90, 180, 90], "precision": 1,
    "point_count": 214, "min_severity": 6,
    "tokens": { "charged": 1, "remaining": 9992 }
  },
  "hotspots": [
    { "lat": 50, "lng": 30, "count": 37, "max_severity": 9, "categories": ["armed_conflict"] }
  ]
}
GET/api/v1/developmentsmetered

The difference between "new to you" and "new in the world". Ask this instead of diffing two /signals responses: it separates events that just happened from updates to events you already knew about, and tells you which half of the change came from the reporting and which from our own observation.

Parameters

ParameterTypeDescription
bboxstringminLon,minLat,maxLon,maxLat. Omit for worldwide.
datestringEnd of the window (YYYY-MM-DD). Defaults to today.
daysinteger 1–30Window length ending at date (1..30, default 7).
categoriesstringComma-separated signal categories.
development_typesstringComma-separated kinds of change. An unknown value is a 400, never an empty result.
notable_onlybooleanDefault true. False returns every recorded change, including minor ones.
limitinteger 1–200How many developments to return (1..200, default 50).
Request
curl -H "Authorization: Bearer ond_..." "https://offnadir-delta.com/api/v1/developments?bbox=22,44,40,53&days=7"
Response
{
  "developments": [
    { "canonical_event_id": "3f0c…", "event_id": 1319087300,
      "development_type": "casualty_count_raised", "source": "world",
      "previous_state": "5", "current_state": "25",
      "change_summary": "The reported number killed rose from 5 to 25.",
      "is_new": false, "detected_at": "2026-08-22T09:15:00Z", "event_occurred_at": "2026-08-19T03:40:00Z" },
    { "canonical_event_id": "91ab…", "event_id": 1319104412,
      "development_type": "imagery_available", "source": "measurement",
      "previous_state": "none", "current_state": "confirmed_post",
      "change_summary": "Post-event imagery now covers this event.", "is_new": false }
  ],
  "meta": { "events_examined": 214, "count": 2, "by_type": { "casualty_count_raised": 1, "imagery_available": 1 } }
}
GET/api/v1/events/{eventId}/threadfree

A news feed cannot tell you whether today’s article is about today’s event. This can: the timeline separates the event from the reporting about it, so an attribution published in 2026 about a 2025 attack reads as an update, not as a new attack.

Parameters

ParameterTypeDescription
eventIdinteger (required)Any signal id belonging to the event.
Request
curl -H "Authorization: Bearer ond_..." "https://offnadir-delta.com/api/v1/events/1319087300/thread"
Response
{
  "canonical_event": {
    "canonical_event_id": "3f0c…", "title": "Red Fort explosion",
    "occurred_at": "2025-11-10T18:00:00Z", "occurred_at_precision": "hour",
    "occurred_at_basis": "stated_in_article",
    "first_reported_at": "2025-11-10T19:15:00Z", "last_reported_at": "2026-08-21T06:00:00Z",
    "casualties_killed": 13, "attributed_actor": "AQIS",
    "attribution_basis": "attributed_by_official", "history_incomplete": false
  },
  "timeline": [
    { "at": "2026-08-21T06:10:00Z", "development_type": "attribution_stated", "source": "world",
      "change_summary": "This event is now attributed to AQIS." }
  ],
  "sources": [{ "event_id": 1319087300, "url": "https://…", "reported_at": "2026-08-21T06:00:00Z" }]
}

Imagery

Search the satellite imagery catalog for scenes over an area, and check the optical-weather outlook before tasking — the natural follow-ups to a signal.

GET/api/v1/imagery2 tok / search

Search the satellite imagery catalog (Sentinel-1, Sentinel-2, NISAR L-band) for scenes over an area and date window — the natural follow-up to a signal. Returns minimal scene metadata (id, datetime, footprint, cloud cover, platform, preview); no imagery bytes and no signed URLs. Pass the event_* parameters to get pre/post timing, SAR pair status, and per-scene target_relation for the event geometry.

Parameters

ParameterTypeDescription
bboxstring (required)Bounding box "minLon,minLat,maxLon,maxLat" (WGS84). Required.
collectionstringCatalog collection. Defaults to sentinel-2-l2a.
datestring (date)Window end date YYYY-MM-DD (UTC). Defaults to today.
daysinteger 1–30Window length in days ending on `date`.
event_datestring (date)Event date YYYY-MM-DD (UTC). When set, each scene is tagged timing=pre/post/same_day_unknown, the window is widened to the canonical pre/post span, and meta reports has_pre_baseline / has_post / bracketing_available / window_status; for sentinel-1-grd it also reports sar_pair_status + orbit_note.
event_pointstringEvent point "lon,lat" (WGS84). When set, each scene reports target_relation (covers_event_geometry = geometry gate; usable_for_analysis additionally requires acceptable cloud for optical) so a scene that only clips the wide bbox — or a cloud-obscured optical scene — is not presented as usable for the event.
event_aoistringEvent AOI bbox "minLon,minLat,maxLon,maxLat" (WGS84) for target_relation.intersects_event_aoi / event_aoi_coverage_ratio.
event_timestampstring (ISO 8601)Full event timestamp (ISO 8601) when known — promotes same-day scenes from same_day_unknown to pre/post by time.
cloud_cover_maxnumber 0–100Sentinel-2 only: max cloud cover percentage.
limitinteger 1–100Max scenes to return.
Request
curl -H "Authorization: Bearer ond_..." \
  "https://offnadir-delta.com/api/v1/imagery?bbox=30.3,50.3,30.7,50.6&collection=sentinel-2-l2a&days=14&cloud_cover_max=30"
Response
{
  "meta": {
    "collection": "sentinel-2-l2a",
    "start_date": "2026-06-28", "end_date": "2026-07-11",
    "bbox": [30.3, 50.3, 30.7, 50.6], "count": 6,
    "tokens": { "charged": 2, "remaining": 9990 }
  },
  "scenes": [
    {
      "id": "S2A_36UUA_20260710_0_L2A",
      "collection": "sentinel-2-l2a",
      "datetime": "2026-07-10T08:56:21Z",
      "bbox": [30.2, 50.2, 30.8, 50.7],
      "cloud_cover": 12.4,
      "platform": "sentinel-2a",
      "preview": "https://…/preview.png"
    }
  ]
}
GET/api/v1/collection/plan4 tokens

One call plans the imagery for one event: SAR and optical each exactly once, against the event footprint, with per-step counts of what actually covers it. Removes the two ways ad-hoc searching goes wrong — skipping the all-weather look, and repeating the same query.

Parameters

ParameterTypeDescription
event_idinteger (required)The signal id from /api/v1/signals.
analysis_goalstring (required)damage_assessment | flood_mapping | wildfire_assessment.
event_datestring (date)Event date YYYY-MM-DD. Optional — the event row supplies it when known.
Request
curl -s "https://offnadir-delta.com/api/v1/collection/plan?event_id=1315064079&analysis_goal=damage_assessment" \
  -H "Authorization: Bearer ond_..."
Response
{
  "meta": { "event_id": 1315064079, "analysis_goal": "damage_assessment", "event_date": "2026-07-24",
            "tokens": { "charged": 4, "remaining": 9984 } },
  "plan": {
    "steps": [
      { "collection": "sentinel-1-grd", "query_reason": "All-weather baseline", "returned_count": 12,
        "usable_count": 0, "sar_pair_status": "catalog_pair_exists_but_geometry_unverified" },
      { "collection": "sentinel-2-l2a", "query_reason": "Human-legible confirmation", "returned_count": 12, "usable_count": 0 }
    ],
    "skipped_duplicates": 0,
    "capability": { "screening": "suitable", "detection": "suitable", "identification": "not_suitable" },
    "vhr_recommendation": { "open_data_still_valid": true }
  }
}
GET/api/v1/collection/priority1 token

Where observation is most worthwhile, and what class of satellite it needs. Separates targets a free Sentinel pass can answer from those that genuinely require commercial sub-metre tasking — the decision that precedes any imagery spend.

Parameters

ParameterTypeDescription
bboxstringArea as "west,south,east,north" (WGS84). Omit for a global survey.
start_datestring (date)Inclusive start date YYYY-MM-DD. Defaults to today; clamped to the plan history floor.
end_datestring (date)Inclusive end date YYYY-MM-DD. Defaults to today. Window capped at 30 days.
categoriesstringComma-separated Delta categories to restrict to.
min_geoint_scorenumberDrop events below this GEOINT score before ranking.
top_ninteger 1–50How many top targets to return (1..50, default 12).
Request
curl -s "https://offnadir-delta.com/api/v1/collection/priority?bbox=30.2,50.2,30.8,50.7&top_n=10" \
  -H "Authorization: Bearer ond_..."
Response
{
  "meta": { "bbox": [30.2, 50.2, 30.8, 50.7], "start_date": "2026-07-25", "end_date": "2026-07-25",
            "tokens": { "charged": 1, "remaining": 9989 } },
  "priority": {
    "by_spec_class": { "vhr": { "count": 4, "mean_importance": 0.81 }, "hr": { "count": 11, "mean_importance": 0.44 } },
    "top": [ { "event_id": 1315064079, "importance": 0.86, "required_class": "vhr", "rs_aoi": [56.2, 27.1, 56.4, 27.3] } ]
  }
}
GET/api/v1/collection/observability1 token

Which events a sensor can actually resolve — exhaustively, not just the top few. The honest answer to "what can we see" before committing collection effort, and cheap enough to run over a whole window.

Parameters

ParameterTypeDescription
sensorstringsentinel-2 (~10 m optical) or sentinel-1 (SAR). Default sentinel-2.
bboxstringArea as "west,south,east,north" (WGS84). Omit for a global survey.
start_datestring (date)Inclusive start date YYYY-MM-DD. Defaults to today; clamped to the plan history floor.
end_datestring (date)Inclusive end date YYYY-MM-DD. Defaults to today. Window capped at 30 days.
categoriesstringComma-separated Delta categories to restrict to.
min_geoint_scorenumberDrop events below this GEOINT score before surveying.
top_ninteger 1–50How many observable events to return (1..50, default 20).
Request
curl -s "https://offnadir-delta.com/api/v1/collection/observability?sensor=sentinel-1&bbox=30.2,50.2,30.8,50.7" \
  -H "Authorization: Bearer ond_..."
Response
{
  "meta": { "bbox": [30.2, 50.2, 30.8, 50.7], "start_date": "2026-07-25", "end_date": "2026-07-25",
            "tokens": { "charged": 1, "remaining": 9988 } },
  "survey": {
    "sensor": "sentinel-1",
    "observable_count": 9,
    "not_observable_count": 23,
    "by_rs_level": { "GSD<10m": 6, "GSD<100m": 3 },
    "top": [ { "event_id": 1315107514, "rs_target": "damaged structures ~10m at Bucha" } ]
  }
}
GET/api/v1/passes2 tokens

When can this place next be imaged, and by what. Free systematic passes (Sentinel/Landsat) are separated from taskable commercial access windows (WorldView / ICEYE / Capella / SkySat), so you can decide whether to wait or to pay — and a failed element fetch says timing is unavailable rather than reporting "no passes".

Parameters

ParameterTypeDescription
latnumber -90–90Target latitude (-90..90; positive = North). Required unless bbox is given.
lonnumber -180–180Target longitude (-180..180; positive = East). Required unless bbox is given.
bboxstringArea as "west,south,east,north" (WGS84). The CENTRE is used as the target when lat/lon are omitted.
start_datestring (date)Inclusive start date YYYY-MM-DD (UTC). Defaults to today.
end_datestring (date)Inclusive end date YYYY-MM-DD (UTC). Defaults to start+2 days; capped to a 7-day horizon.
satellitesstringComma-separated family keys to consider: sentinel-1, sentinel-2, landsat, worldview, iceye, capella, skysat. Omit for all seven.
max_passesinteger 1–100Maximum passes to return, soonest first (1..100, default 40).
Request
curl -s "https://offnadir-delta.com/api/v1/passes?lat=27.1842&lon=56.2893&satellites=sentinel-1,sentinel-2" \
  -H "Authorization: Bearer ond_..."
Response
{
  "meta": {
    "target": { "lat": 27.1842, "lon": 56.2893 },
    "window": { "start": "2026-07-25T00:00:00Z", "end": "2026-07-27T23:59:59Z" },
    "tokens": { "charged": 2, "remaining": 9988 }
  },
  "passes": [
    {
      "satellite": "SENTINEL-1C",
      "family_key": "sentinel-1",
      "sensor": "sar",
      "collection_mode": "systematic",
      "peak": "2026-07-26T02:14:08Z",
      "off_nadir_deg": 21.4,
      "direction": "descending",
      "sunlit": false,
      "tle_age_days": 1.2
    }
  ],
  "freshness": { "max_tle_age_days": 1.2, "stale": false },
  "retrieval_ok": true,
  "note": "Systematic (Sentinel/Landsat) passes are scheduled acquisitions.",
  "attribution": "Orbital data by CelesTrak"
}

Measure

Put a number on the ground itself: how an index moved across the archive, how many vessels are in a scene, what the terrain does to a radar look. These measure imagery on demand — Monitoring, further down, is the same measurements repeated as new acquisitions arrive.

GET/api/v1/elevationfree

Height, and the numbers that depend on it. Relief (max − min) is the one that matters operationally: it governs how much of an area radar loses to layover and shadow, so a fabricated relief produces fabricated collection advice. Land only — no coverage over water is reported as absence, never as 0 m.

Parameters

ParameterTypeDescription
latnumber -90–90Latitude of a single point (use with lon).
lonnumber -180–180Longitude of a single point (use with lat).
bboxstringArea as "lon_min,lat_min,lon_max,lat_max" (WGS84).
Request
curl -H "Authorization: Bearer ond_..." \
  "https://offnadir-delta.com/api/v1/elevation?bbox=138.70,35.34,138.76,35.38"
Response
{
  "mode": "bbox",
  "elevation": { "min_m": 852.4, "max_m": 3768.4, "mean_m": 1892.7, "median_m": 1740.2,
                 "p10_m": 1004.1, "p90_m": 2988.6, "relief_m": 2916.0,
                 "sample_spacing_m": 31, "downsampled": true, "measured_over": "bbox" },
  "attribution": "Produced using Copernicus WorldDEM™-30 …"
}
POST/api/v1/elevationfree

The same measurement clipped to the ring you drew rather than the box around it. For an L-shaped or diagonal AOI those are different areas, and reporting the box as "the polygon" is the overstatement this endpoint exists to avoid — the response names the clip in `measured_over`.

Parameters

ParameterTypeDescription
polygonarray[]WGS84 ring [[lon, lat], …] with at least 3 vertices. A GeoJSON Polygon or Feature is also accepted.
bboxnumber[]Alternative to polygon: [lon_min, lat_min, lon_max, lat_max].
Request
curl -X POST -H "Authorization: Bearer ond_..." \
  -H "Content-Type: application/json" \
  -d '{"polygon": [[138.72,35.34],[138.76,35.36],[138.73,35.38],[138.70,35.36]]}' \
  "https://offnadir-delta.com/api/v1/elevation"
Response
{
  "mode": "polygon",
  "elevation": { "min_m": 1204.8, "max_m": 3768.4, "mean_m": 2311.5, "relief_m": 2563.6,
                 "measured_over": "polygon_interior", "polygon_vertices": 4 },
  "attribution": "Produced using Copernicus WorldDEM™-30 …"
}
GET/api/v1/terrainfree

The arithmetic behind "can radar actually use this ground", and behind "can this position see that one". Both change with inputs that must not be assumed: layover measured on one volcanic flank was 2.6% looking west against 0.7% looking east at the same 35°, which is why the geometry is required rather than defaulted. Both answers state the sample spacing they were computed at — a coarser grid reads flatter, and therefore more observable, than the ground is.

Parameters

ParameterTypeDescription
operationstring (required)Which computation to run.
bboxstringFor sar_geometry: area as "lon_min,lat_min,lon_max,lat_max" (WGS84).
incidence_degnumber 10–80For sar_geometry: sensor incidence angle. Sentinel-1 IW spans roughly 29-46 degrees.
look_azimuth_degnumber 0–360For sar_geometry: compass bearing the sensor looks along the ground range. A right-looking descending pass looks roughly west (270).
latnumber -90–90For profile: latitude of the observer end.
lonnumber -180–180For profile: longitude of the observer end.
to_latnumber -90–90For profile: latitude of the far end.
to_lonnumber -180–180For profile: longitude of the far end.
observer_height_mnumberFor profile: eye height above the ground, default 2 m. Use the real mast or tower height when that is the question.
Request
curl -H "Authorization: Bearer ond_..." \
  "https://offnadir-delta.com/api/v1/terrain?operation=sar_geometry&bbox=138.70,35.34,138.76,35.38&incidence_deg=35&look_azimuth_deg=270"
Response
{
  "operation": "sar_geometry",
  "sar_geometry": { "layover_fraction": 0.026, "shadow_fraction": 0.0,
                    "foreshortening_fraction": 0.517, "usable_fraction": 0.457,
                    "mean_local_incidence_deg": 32.7, "sample_spacing_m": 31 },
  "interpretation": "3% of the area is in layover at this geometry …"
}
POST/api/v1/signals/{id}/refine-location3–29 tokens, 0 if it does not improve

Most signals are geolocated once, for free, from one article’s place string — about two thirds sit at ±5 km or have no bounded radius at all. This buys a second, deeper read of the sources for one signal you care about.

Parameters

ParameterTypeDescription
idinteger (required)The signal (global_event_id) whose location should be researched further.
Request
curl -X POST -H "Authorization: Bearer ond_..." \
  "https://offnadir-delta.com/api/v1/signals/1318224420/refine-location"
Response
{
  "summary": "Refined from locality precision (±5000 m) to site precision (±500 m) on a source that names "Narita International Airport, Chiba, Japan".",
  "result": {
    "improved": true,
    "before": { "granularity": "locality", "uncertainty_m": 5000, "footprint_kind": "area" },
    "after":  { "granularity": "site", "uncertainty_m": 500, "footprint_kind": "point" },
    "evidence": [{ "url": "https://…", "publisher": "…", "place_named": "Narita International Airport, Chiba, Japan", "origin": "live_search" }],
    "tokens": { "charged": 14, "maximum_possible": 29 }
  }
}
POST/api/v1/index-series0.5 tokens / scene measured

The retrospective half of Monitoring: the same measurement, run backwards over the archive instead of forwards over new acquisitions. Estimate first — it is free, and it is the only way to know the cost before spending it.

Parameters

ParameterTypeDescription
polygonarray[]WGS84 ring [[lon, lat], …] with at least 3 vertices. Statistics cover the samples INSIDE it. A GeoJSON Polygon or Feature is also accepted.
bboxnumber[4]Alternative to polygon: [lon_min, lat_min, lon_max, lat_max] (WGS84).
indexstring (required)Which optical index to measure: ndvi, evi, savi, ndmi, ndwi, mndwi, ndbi, ndsi, nbr, iron-oxide, clay, ferrous.
startstring (date) (required)Start date YYYY-MM-DD (UTC). Clamped forward to 2015-06-27 if earlier.
endstring (date) (required)End date YYYY-MM-DD (UTC).
max_scenesinteger 1–24Cap on scenes measured in this call. Server maximum 24.
max_cloud_covernumber 1–100Scene cloud-cover ceiling in percent. Default 30.
estimate_onlybooleanTrue = free: return the scene count, date span and token cost WITHOUT measuring or charging.
Request
curl -X POST -H "Authorization: Bearer ond_..." \
  -H "Content-Type: application/json" \
  -d '{"bbox": [67.64, 33.31, 67.74, 33.84], "index": "ndvi", "start": "2016-01-01", "end": "2026-08-01", "estimate_only": true}' \
  "https://offnadir-delta.com/api/v1/index-series"
Response
{
  "summary": "322 Sentinel-2 scene(s) match. The 100 examined span 2023-02-06 to 2026-07-25. Measuring 24 of them would cost 12 token(s) (0.5 per scene).",
  "estimate": { "index": "ndvi", "scenes_found": 322, "scenes_examined": 100, "scenes_measurable": 24,
                "truncated": true, "archive_start": "2015-06-27", "estimated_tokens": 12,
                "candidate_date_span": { "first": "2023-02-06", "last": "2026-07-25" },
                "notes": ["Only the newest 100 of those 322 were examined, so the earliest date shown here is the edge of that page — not the start of the archive …"] },
  "charged": false
}
POST/api/v1/ships5 tokens / detection

Radar sees through cloud and at night, so a vessel count works when optical would not. The number alone is not the answer — what the land mask did, and how much of your area the scene actually covers, decide what it means.

Parameters

ParameterTypeDescription
collectionstring (required)Catalog collection of the scene.
item_idstring (required)STAC item id of the scene, as returned by GET /api/v1/imagery.
bboxnumber[4]Area to search within the scene, [lon_min, lat_min, lon_max, lat_max] (WGS84). Must be under 120,000 km².
geometryobjectAlternative to bbox: a GeoJSON Polygon or MultiPolygon.
algorithm_versionstringDetector version. 'auto' picks the recommended version for the sensor.
Request
curl -X POST -H "Authorization: Bearer ond_..." \
  -H "Content-Type: application/json" \
  -d '{"collection": "sentinel-1-grd", "item_id": "S1A_IW_GRDH_1SDV_20260801T031500", "bbox": [37.79, 44.69, 37.83, 44.72]}' \
  "https://offnadir-delta.com/api/v1/ships"
Response
{
  "summary": "14 vessel-like target(s) detected over the requested area (sentinel-1-grd, algorithm v3). Detections within 300 m of the coast were excluded, so vessels alongside a quay are not in this count.",
  "count": 14,
  "caveats": ["Detections within 300 m of the coast were excluded, …"],
  "processing": { "resolved_algorithm_version": "v3", "sea_ratio": 0.62, "land_mask_source": "osm:land_polygons" }
}

Brief

The AI-synthesized Daily World Brief (free).

GET/api/v1/brieffree

The Daily World Brief — an AI-synthesized digest of the previous day’s worldwide event signals (headline, executive summary, top developments, ranked signals). Free of token charges.

Parameters

ParameterTypeDescription
datestring (date)Brief date YYYY-MM-DD (UTC). Defaults to the latest available.
Request
curl -H "Authorization: Bearer ond_..." "https://offnadir-delta.com/api/v1/brief"
Response
{
  "brief": {
    "date": "2026-07-07",
    "headline": "…",
    "summary": "…",
    "developments": [ { "title": "…", "why_it_matters": "…" } ],
    "key_judgments": [ … ]
  }
}

Intelligence (AI)

AI assessment and the agentic analyst — available on every plan, metered on your token balance.

POST/api/v1/assess5/15 tok · AI

AI remote-sensing deep-dive for a single signal: what to observe, recommended sensors, and a collection window. `eventId` is the `id` from a signals result. A prior (event, kind) assessment is cached and not re-charged. Available on every plan, including Free; metered on your token balance.

Parameters

ParameterTypeDescription
eventIdinteger (required)Signal id (global_event_id) from /api/v1/signals.
kindstringAssessment depth. quick=5 tok, deep=15 tok. Defaults to quick.
Request
curl -X POST -H "Authorization: Bearer ond_..." \
  -H "Content-Type: application/json" \
  -d '{"eventId": 1312473707, "kind": "quick"}' \
  "https://offnadir-delta.com/api/v1/assess"
Response
{
  "kind": "quick",
  "cached": false,
  "model": "…",
  "content": {
    "summary": "…",
    "observability": "…",
    "recommendedSensors": ["SAR satellites", "high-resolution optical"]
  },
  "meta": { "tokens": { "charged": 5, "remaining": 195 } }
}
POST/api/v1/analyst5–123 tok · AI

Ask the Delta Analyst an OSINT/GEOINT question. Runs an agentic multi-step analysis and returns a structured brief (summary, findings with collection recommendations, assessment, citations). Usage-based pricing: 5–123 tokens metered by the compute the question actually uses (the exact charge is returned in meta.tokens.charged). Hybrid async: the call waits up to ~95s and returns the brief (200); a longer run returns 202 with a job_id to poll at GET /api/v1/analyst/{jobId}. Send an Idempotency-Key header to make a timeout or a retry safe — the same key resolves to the same run with no second charge. Available on every plan, including Free; metered on your token balance.

Parameters

ParameterTypeDescription
Idempotency-KeystringAt-most-once key. A re-send with the same key resolves to the SAME run: done → the brief (no re-charge); still running → 202 with the job_id.
questionstring (required)The analytic question (≤ 500 chars).
bboxnumber[4]Optional focus bounding box [minLon, minLat, maxLon, maxLat] (WGS84).
modestringfast (default) or deep. Deep enables extended reasoning and wider evidence-gathering budgets for forecasting, collection trade-offs and market-implication questions; it is slower and the ceiling rises from 123 to 415 tokens. Charging stays metered by what the run actually consumes.
Request
curl -X POST -H "Authorization: Bearer ond_..." \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: my-question-2026-07-25-1" \
  -d '{"question": "Key escalation signals in the Black Sea this week?"}' \
  "https://offnadir-delta.com/api/v1/analyst"
Response
{
  "brief": {
    "summary": "…",
    "findings": [ { "global_event_id": 131…, "recommended_sensor": "SAR", "rationale": "…" } ],
    "assessment": "…",
    "citations": [ "https://…" ]
  },
  "meta": { "tokens": { "charged": 12, "remaining": 150 } }
}
GET/api/v1/analyst/{jobId}free

Poll an analyst run started by POST /api/v1/analyst. Free — the run itself is charged once, on completion. While the job is processing the response carries a progress block (stage plus completed_steps / total_steps); completed_steps reaches total_steps only when status is "done".

Parameters

ParameterTypeDescription
jobIdstring (uuid) (required)The job_id returned by POST /api/v1/analyst.
Request
curl -H "Authorization: Bearer ond_..." \
  "https://offnadir-delta.com/api/v1/analyst/c27a5d66-64a6-4785-aa9a-ea182e23efa6"
Response
{
  "job_id": "c27a5d66-64a6-4785-aa9a-ea182e23efa6",
  "status": "processing",
  "progress": { "stage": "synthesis", "completed_steps": 3, "total_steps": 6 },
  "created_at": "2026-07-25T09:12:00Z",
  "updated_at": "2026-07-25T09:12:41Z"
}

Evidence

The claim ledger: what you were told, how well established it was, and whether a later answer restated it — the audit trail behind the answers.

GET/api/v1/claimsfree

What you were told, and whether it held. Each claim keeps its evidence class and independent source count, and — the part no other feed gives you — a link to the later answer that restated it, saying whether the evidence got stronger or weaker. Audit before you act on it.

Parameters

ParameterTypeDescription
sincestringOnly claims asserted on or after this date (YYYY-MM-DD or ISO 8601).
evidence_classstringRestrict to one evidence class.
restated_onlybooleanOnly claims that sit in a restatement chain.
downgraded_onlybooleanOnly claims a later answer restated with WEAKER evidence — read these first.
limitinteger 1–200How many claims to return (1..200, default 50).
Request
curl -H "Authorization: Bearer ond_..." "https://offnadir-delta.com/api/v1/claims?downgraded_only=true"
Response
{
  "claims": [
    { "claim_id": "8f3c…", "text": "US fired on a ship attempting to break the blockade…",
      "evidence_class": "REPORTED", "independent_source_count": 2,
      "sources": ["reuters.com", "apnews.com"],
      "restated_by": { "claim_id": "b21a…", "evidence_class": "CONFIRMED",
                       "what_changed": "A later answer restated this with STRONGER evidence." } }
  ],
  "counts": { "total": 1, "by_evidence_class": { "REPORTED": 1 }, "restated": 1, "downgraded": 0 },
  "summary": "1 recorded claim(s): 1 REPORTED. 1 sit in a restatement chain."
}

Standing Orders

Put an area under continuous watch. A deterministic pass decides whether anything crossed the reporting bar, so quiet periods cost nothing — only a firing check runs the Analyst and is metered.

GET/api/v1/standing-ordersfree

What you are currently watching, and what your plan still allows. quiet_checks is the honest metric here — a watch that has found nothing for many cycles is one you should retire.

Request
curl -H "Authorization: Bearer ond_..." "https://offnadir-delta.com/api/v1/standing-orders"
Response
{
  "orders": [
    { "id": "8f3c…", "name": "Kyiv oblast", "cadence": "daily", "bbox": [30.3, 50.3, 30.7, 50.6],
      "last_checked_at": "2026-07-26T09:15:00Z", "last_run_at": "2026-07-24T09:15:00Z",
      "quiet_checks": 2, "total_runs": 6 }
  ],
  "limits": { "max_orders": 5, "used": 1, "remaining": 4, "min_interval_hours": 24 }
}
POST/api/v1/standing-ordersfree to create · metered per fire

Turn a question into a watch. The cost is per CHANGE, not per check: a deterministic pass over the corpus decides whether anything crossed the reporting bar, and quiet periods never invoke the model. Use this when the question is "tell me when this changes" rather than "what is happening now".

Parameters

ParameterTypeDescription
bboxnumber[4] (required)Area to watch, [minLon, minLat, maxLon, maxLat] (WGS84). Required — a global watch would fire on everything.
questionstringThe question to re-answer when something changes (≤ 500 chars). Defaults to "what changed in this area, and what does it mean?".
namestringLabel for the order.
cadencestringHow often to CHECK — checking is free, only a firing check costs tokens. Default weekly.
categoriesstring[]Restrict the watch to these Delta categories.
min_geoint_scorenumber 0–10Reporting bar 0–10 (default 6). Raise it to hear only about major developments.
min_new_eventsnumber 1–50How many new qualifying events must appear before a run is triggered (default 1).
notify_emailbooleanEmail the result when it fires (default true). Results are readable from the list either way.
Request
curl -X POST -H "Authorization: Bearer ond_..." \
  -H "Content-Type: application/json" \
  -d '{"name": "Kyiv oblast", "bbox": [30.3, 50.3, 30.7, 50.6], "cadence": "daily",
       "question": "What changed in the strike pattern, and what does it imply?"}' \
  "https://offnadir-delta.com/api/v1/standing-orders"
Response
{
  "order": { "id": "8f3c…", "name": "Kyiv oblast", "cadence": "daily", "next_due_at": "2026-07-26T09:20:00Z" },
  "projected_monthly_tokens_typical": 1370,
  "projected_monthly_tokens_max": 3744
}
DELETE/api/v1/standing-orders/{orderId}free

Stop watching. Deleting frees the plan slot; pausing (POST with active:false) keeps the order and its history but not the slot.

Parameters

ParameterTypeDescription
orderIdstring (uuid) (required)The id returned when the order was created.
Request
curl -X DELETE -H "Authorization: Bearer ond_..." \
  "https://offnadir-delta.com/api/v1/standing-orders/8f3c1a2b-…"
Response
{ "deleted": "8f3c1a2b-…" }
POST/api/v1/standing-orders/{orderId}free

Pause a watch without losing it. Resuming starts from now rather than replaying the gap — a queue of stale alerts is worse than no alert.

Parameters

ParameterTypeDescription
orderIdstring (uuid) (required)The id returned when the order was created.
activeboolean (required)false = pause, true = resume.
Request
curl -X POST -H "Authorization: Bearer ond_..." \
  -H "Content-Type: application/json" -d '{"active": false}' \
  "https://offnadir-delta.com/api/v1/standing-orders/8f3c1a2b-…"
Response
{ "order": { "id": "8f3c1a2b-…", "active": false } }

Monitoring

Delta Monitor: fix a place, pick a quantity, and every new acquisition over it is measured automatically. Reading is free; a check is metered only when it finds new imagery to measure.

GET/api/v1/monitoringfree

What you are currently measuring, and the latest number for each. coverage is the honest field here: measured counts what we actually computed, and window_total is null rather than 0 when we have not asked the catalog how many acquisitions exist.

Request
curl -H "Authorization: Bearer ond_..." "https://offnadir-delta.com/api/v1/monitoring"
Response
{
  "areas": [
    { "area_id": "2b5f…", "name": "Strait of Hormuz", "status": "anomaly",
      "bbox": [56.0, 26.4, 56.8, 27.0],
      "metrics": [
        { "polygon_id": "2b5f…", "metric": "ship_detection", "metric_label": "ships",
          "satellite": "sentinel-1-grd", "latest": { "date": "2026-07-30", "value": 31, "is_anomaly": true },
          "delta": 18, "coverage": { "measured": 12, "acquired": 18, "window_total": null } }
      ] }
  ],
  "limits": { "max_areas": 1, "used": 1, "remaining": 0 }
}
POST/api/v1/monitoringfree to create · metered per scene

Turn a place into a time series. The cost follows the imagery, not the calendar: a check that finds no new acquisition measures nothing and charges nothing. Use this when the question is about a quantity at a fixed place over time, rather than about events — that is a standing order.

Parameters

ParameterTypeDescription
bboxnumber[4] (required)Area to measure, [minLon, minLat, maxLon, maxLat] (WGS84). Must be under 5,000 km².
metricstring (required)What to count. Plain words work (ships, fires, vegetation, water, burn, snow, built_up, moisture, night_lights) as do index names (ship_detection, fire_count, ndvi, evi, savi, ndmi, ndwi, mndwi, ndbi, ndsi, nbr, dnb, vv, vh, rvi, rfdi, cr).
namestringLabel for the area.
start_datestring (date)YYYY-MM-DD to begin the history from (default 30 days ago). A longer backfill measures more scenes and costs more on the first check.
Request
curl -X POST -H "Authorization: Bearer ond_..." \
  -H "Content-Type: application/json" \
  -d '{"name": "Sheskharis berth", "bbox": [37.79, 44.69, 37.83, 44.72], "metric": "ships"}' \
  "https://offnadir-delta.com/api/v1/monitoring"
Response
{
  "area": { "area_id": "2b5f…", "name": "Sheskharis berth", "status": "ok",
            "metrics": [{ "metric": "ship_detection", "satellite": "sentinel-1-grd", "latest": null }] },
  "metering": { "create": "free", "per_check_tokens": 2, "per_scene_tokens": 0.5 }
}
GET/api/v1/monitoring/{areaId}free

The series behind the number — what to read when the question is "is it going up", "when did it change", or "how unusual is today". Every point carries the STAC item it was measured from, so any value can be traced back to a specific acquisition.

Parameters

ParameterTypeDescription
areaIdstring (uuid) (required)The area_id from the list endpoint (a metric polygon_id also resolves).
Request
curl -H "Authorization: Bearer ond_..." "https://offnadir-delta.com/api/v1/monitoring/2b5f1a2b-…"
Response
{
  "area": { "area_id": "2b5f…", "name": "Strait of Hormuz", "status": "anomaly",
    "metrics": [{ "metric": "ship_detection", "metric_label": "ships",
      "series": [ { "date": "2026-07-18", "value": 13, "is_anomaly": false, "stac_item_id": "S1A_IW_…" },
                  { "date": "2026-07-30", "value": 31, "is_anomaly": true,  "stac_item_id": "S1A_IW_…" } ] }] }
}

Usage

Your token balance and plan capabilities (free) — pre-flight a metered call.

GET/api/v1/usagefree

Your key’s current token balance and plan capabilities — call it to pre-flight a metered request without spending anything.

Request
curl -H "Authorization: Bearer ond_..." "https://offnadir-delta.com/api/v1/usage"
Response
{
  "tokens": { "allocation": 10000, "used": 42, "remaining": 9958 },
  "plan": { "api_llm_access": true }
}
GET/api/v1/statusfree

Pre-flight the data, not just the balance. If the pipeline is behind, a metered query still succeeds and still charges — it just answers from an older corpus than you assumed. This is how you find that out first.

Request
curl -H "Authorization: Bearer ond_..." "https://offnadir-delta.com/api/v1/status"
Response
{
  "pipeline_status": "ok",
  "data_current_through": "2026-08-14T06:00:00Z",
  "server": { "version": "1.11.0", "tool_count": 24, "environment": "production" }
}
GET/api/v1/versionfree · no key needed

The only unauthenticated endpoint. Its purpose is drift detection: MCP has no handshake for a changed tool set, so a connector can cache a stale roster indefinitely. Compare the three fingerprints against what your client cached — if they differ, reconnect.

Request
curl "https://offnadir-delta.com/api/v1/version"
Response
{
  "name": "off-nadir-delta", "version": "1.11.0", "tool_count": 24, "toolset": "standard",
  "schema_hash": "7d71fd6d9f4e", "description_hash": "256e28bd5a61", "descriptor_hash": "96beb5b8b825",
  "environment": "production"
}

MCP server

An MCP (Model Context Protocol) server at /api/v1/mcp exposes the same surface as tools, plus resources and prompts, so any MCP-capable agent can consume the intelligence directly.

  • query_signals · query_stats · query_hotspots · get_world_brief · get_usage · search_imagery · plan_event_imagery · rank_imaging_priority · survey_observable_events · predict_satellite_passes · get_analyst_job · query_claims · query_developments · get_event_thread · create_standing_order · list_standing_orders · delete_standing_order · list_monitored_areas · get_monitored_area · create_monitored_area · lookup_elevation · analyze_terrain · measure_index_series · detect_ships — tools (metered / free)
  • assess_signal · ask_analyst · refine_location — AI tools
  • Resources: brief://latest, signals://schema, usage://current, imagery://collections, status://current, brief://{date}
  • Prompts: daily-situation-briefing, assess-top-signal, aoi-watch, market-exposure-check

Claude & other web apps (OAuth)

Web apps such as Claude (claude.ai) connect over OAuth — no API key to paste. Add a custom connector (Settings → Connectors → Add custom connector) and enter the MCP URL below. The endpoint is an OAuth 2.1 resource server with discovery, dynamic client registration, and PKCE, so the app completes the flow for you — just sign in to Off-Nadir Delta and approve. Metering and plan gating are billed to that account.

Custom connector URL
https://offnadir-delta.com/api/v1/mcp

Claude Code

Claude Code
claude mcp add --transport http off-nadir-delta \
  https://offnadir-delta.com/api/v1/mcp \
  --header "Authorization: Bearer ond_..."

Config file (API key)

Config file
{
  "mcpServers": {
    "off-nadir-delta": {
      "type": "http",
      "url": "https://offnadir-delta.com/api/v1/mcp",
      "headers": { "Authorization": "Bearer ond_..." }
    }
  }
}

OpenAPI spec

A machine-readable OpenAPI 3.1 document is available for codegen and API explorers:

https://offnadir-delta.com/api/v1/openapi.json

From headline to satellite evidence

One connected intelligence workflow across four surfaces — free to start, no GIS software or remote-sensing background required.