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_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxErrors
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..."
}| 401 | Missing or invalid key |
| 403 | Your plan does not include this endpoint |
| 402 | Insufficient tokens for a metered call |
| 400 | Invalid parameters (see the message) |
| 404 | Resource not found |
| 429 | Rate 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.
/api/v1/signals3 tok / pageGeolocated 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
| Parameter | Type | Description |
|---|---|---|
| bbox | string | Bounding box "minLon,minLat,maxLon,maxLat" (WGS84). Omit for worldwide. |
| date | string (date) | Window end date YYYY-MM-DD (UTC). Defaults to today. |
| days | integer 1–30 | Window length in days ending on `date`. |
| categories | string | Comma-separated category filter. Omit for all. |
| markets | string | Comma-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. |
| limit | integer 1–500 | Max rows per page. |
| cursor | string | Opaque pagination cursor from a previous response's meta.next_cursor. |
| min_severity | number 0–10 | Keep only signals with severity_score >= this (0-10). |
| escalating | boolean | Set true to keep only signals whose escalation_trend is "escalating". |
| sort | string | Result 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_since | string (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_since | string (ISO 8601) | Differential fetch: only signals first enriched at/after this ISO 8601 timestamp. |
| observability | string | Keep only signals with this satellite observability — is a physical mark imageable at all (intelligence.satellite_observability). |
| observability_status | string | 3-state observability filter (observable / not_observable / insufficient_detail). Exposes the insufficient_detail bucket that the binary observability filter excludes from both sides. |
| open_data | string | Keep only signals with this open-data sufficiency — free imagery enough vs commercial tasking recommended (intelligence.open_data_sufficiency). |
| min_information_gain | number 0–1 | Keep only signals whose intelligence.expected_information_gain >= this (0-1). |
| taskable_only | boolean | Keep 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_only | boolean | STRICT 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. |
curl -H "Authorization: Bearer ond_..." \
"https://offnadir-delta.com/api/v1/signals?days=1&categories=armed_conflict&limit=2"{
"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
}
}
]
}/api/v1/signals/{id}1 tokenRe-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
| Parameter | Type | Description |
|---|---|---|
| id | integer (required) | The signal id (global_event_id) from a /api/v1/signals result. |
curl -H "Authorization: Bearer ond_..." "https://offnadir-delta.com/api/v1/signals/1312473707"{
"signal": { "id": 1312473707, "category": "armed_conflict", "severity": 7.4,
"verification_status": "independently_confirmed", "independent_source_count": 4 },
"meta": { "tokens": { "charged": 1 } }
}/api/v1/signals/stats1 tok / queryAggregate 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
| Parameter | Type | Description |
|---|---|---|
| bbox | string | Bounding box "minLon,minLat,maxLon,maxLat" (WGS84). Omit for worldwide. |
| date | string (date) | Window end date YYYY-MM-DD (UTC). Defaults to today. |
| days | integer 1–30 | Window length in days ending on `date`. |
| categories | string | Comma-separated category filter. Omit for all. |
curl -H "Authorization: Bearer ond_..." \
"https://offnadir-delta.com/api/v1/signals/stats?days=7&categories=armed_conflict"{
"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 }]
}
}/api/v1/signals/hotspots1 tok / queryGeographic 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
| Parameter | Type | Description |
|---|---|---|
| bbox | string | Bounding box "minLon,minLat,maxLon,maxLat" (WGS84). Omit for worldwide. |
| date | string (date) | Window end date YYYY-MM-DD (UTC). Defaults to today. |
| days | integer 1–30 | Window length in days ending on `date`. |
| categories | string | Comma-separated category filter. Omit for all. |
| precision | number 0.1–5 | Grid cell size in decimal degrees. |
| min_severity | number 0–10 | Keep only points with severity_score >= this (0-10). |
| limit | integer 1–500 | Max 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. |
curl -H "Authorization: Bearer ond_..." \
"https://offnadir-delta.com/api/v1/signals/hotspots?days=3&precision=1&min_severity=6"{
"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"] }
]
}/api/v1/developmentsmeteredThe 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
| Parameter | Type | Description |
|---|---|---|
| bbox | string | minLon,minLat,maxLon,maxLat. Omit for worldwide. |
| date | string | End of the window (YYYY-MM-DD). Defaults to today. |
| days | integer 1–30 | Window length ending at date (1..30, default 7). |
| categories | string | Comma-separated signal categories. |
| development_types | string | Comma-separated kinds of change. An unknown value is a 400, never an empty result. |
| notable_only | boolean | Default true. False returns every recorded change, including minor ones. |
| limit | integer 1–200 | How many developments to return (1..200, default 50). |
curl -H "Authorization: Bearer ond_..." "https://offnadir-delta.com/api/v1/developments?bbox=22,44,40,53&days=7"{
"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 } }
}/api/v1/events/{eventId}/threadfreeA 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
| Parameter | Type | Description |
|---|---|---|
| eventId | integer (required) | Any signal id belonging to the event. |
curl -H "Authorization: Bearer ond_..." "https://offnadir-delta.com/api/v1/events/1319087300/thread"{
"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.
/api/v1/imagery2 tok / searchSearch 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
| Parameter | Type | Description |
|---|---|---|
| bbox | string (required) | Bounding box "minLon,minLat,maxLon,maxLat" (WGS84). Required. |
| collection | string | Catalog collection. Defaults to sentinel-2-l2a. |
| date | string (date) | Window end date YYYY-MM-DD (UTC). Defaults to today. |
| days | integer 1–30 | Window length in days ending on `date`. |
| event_date | string (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_point | string | Event 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_aoi | string | Event AOI bbox "minLon,minLat,maxLon,maxLat" (WGS84) for target_relation.intersects_event_aoi / event_aoi_coverage_ratio. |
| event_timestamp | string (ISO 8601) | Full event timestamp (ISO 8601) when known — promotes same-day scenes from same_day_unknown to pre/post by time. |
| cloud_cover_max | number 0–100 | Sentinel-2 only: max cloud cover percentage. |
| limit | integer 1–100 | Max scenes to return. |
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"{
"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"
}
]
}/api/v1/collection/plan4 tokensOne 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
| Parameter | Type | Description |
|---|---|---|
| event_id | integer (required) | The signal id from /api/v1/signals. |
| analysis_goal | string (required) | damage_assessment | flood_mapping | wildfire_assessment. |
| event_date | string (date) | Event date YYYY-MM-DD. Optional — the event row supplies it when known. |
curl -s "https://offnadir-delta.com/api/v1/collection/plan?event_id=1315064079&analysis_goal=damage_assessment" \
-H "Authorization: Bearer ond_..."{
"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 }
}
}/api/v1/collection/priority1 tokenWhere 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
| Parameter | Type | Description |
|---|---|---|
| bbox | string | Area as "west,south,east,north" (WGS84). Omit for a global survey. |
| start_date | string (date) | Inclusive start date YYYY-MM-DD. Defaults to today; clamped to the plan history floor. |
| end_date | string (date) | Inclusive end date YYYY-MM-DD. Defaults to today. Window capped at 30 days. |
| categories | string | Comma-separated Delta categories to restrict to. |
| min_geoint_score | number | Drop events below this GEOINT score before ranking. |
| top_n | integer 1–50 | How many top targets to return (1..50, default 12). |
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_..."{
"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] } ]
}
}/api/v1/collection/observability1 tokenWhich 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
| Parameter | Type | Description |
|---|---|---|
| sensor | string | sentinel-2 (~10 m optical) or sentinel-1 (SAR). Default sentinel-2. |
| bbox | string | Area as "west,south,east,north" (WGS84). Omit for a global survey. |
| start_date | string (date) | Inclusive start date YYYY-MM-DD. Defaults to today; clamped to the plan history floor. |
| end_date | string (date) | Inclusive end date YYYY-MM-DD. Defaults to today. Window capped at 30 days. |
| categories | string | Comma-separated Delta categories to restrict to. |
| min_geoint_score | number | Drop events below this GEOINT score before surveying. |
| top_n | integer 1–50 | How many observable events to return (1..50, default 20). |
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_..."{
"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" } ]
}
}/api/v1/passes2 tokensWhen 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
| Parameter | Type | Description |
|---|---|---|
| lat | number -90–90 | Target latitude (-90..90; positive = North). Required unless bbox is given. |
| lon | number -180–180 | Target longitude (-180..180; positive = East). Required unless bbox is given. |
| bbox | string | Area as "west,south,east,north" (WGS84). The CENTRE is used as the target when lat/lon are omitted. |
| start_date | string (date) | Inclusive start date YYYY-MM-DD (UTC). Defaults to today. |
| end_date | string (date) | Inclusive end date YYYY-MM-DD (UTC). Defaults to start+2 days; capped to a 7-day horizon. |
| satellites | string | Comma-separated family keys to consider: sentinel-1, sentinel-2, landsat, worldview, iceye, capella, skysat. Omit for all seven. |
| max_passes | integer 1–100 | Maximum passes to return, soonest first (1..100, default 40). |
curl -s "https://offnadir-delta.com/api/v1/passes?lat=27.1842&lon=56.2893&satellites=sentinel-1,sentinel-2" \
-H "Authorization: Bearer ond_..."{
"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.
/api/v1/elevationfreeHeight, 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
| Parameter | Type | Description |
|---|---|---|
| lat | number -90–90 | Latitude of a single point (use with lon). |
| lon | number -180–180 | Longitude of a single point (use with lat). |
| bbox | string | Area as "lon_min,lat_min,lon_max,lat_max" (WGS84). |
curl -H "Authorization: Bearer ond_..." \
"https://offnadir-delta.com/api/v1/elevation?bbox=138.70,35.34,138.76,35.38"{
"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 …"
}/api/v1/elevationfreeThe 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
| Parameter | Type | Description |
|---|---|---|
| polygon | array[] | WGS84 ring [[lon, lat], …] with at least 3 vertices. A GeoJSON Polygon or Feature is also accepted. |
| bbox | number[] | Alternative to polygon: [lon_min, lat_min, lon_max, lat_max]. |
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"{
"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 …"
}/api/v1/terrainfreeThe 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
| Parameter | Type | Description |
|---|---|---|
| operation | string (required) | Which computation to run. |
| bbox | string | For sar_geometry: area as "lon_min,lat_min,lon_max,lat_max" (WGS84). |
| incidence_deg | number 10–80 | For sar_geometry: sensor incidence angle. Sentinel-1 IW spans roughly 29-46 degrees. |
| look_azimuth_deg | number 0–360 | For sar_geometry: compass bearing the sensor looks along the ground range. A right-looking descending pass looks roughly west (270). |
| lat | number -90–90 | For profile: latitude of the observer end. |
| lon | number -180–180 | For profile: longitude of the observer end. |
| to_lat | number -90–90 | For profile: latitude of the far end. |
| to_lon | number -180–180 | For profile: longitude of the far end. |
| observer_height_m | number | For profile: eye height above the ground, default 2 m. Use the real mast or tower height when that is the question. |
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"{
"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 …"
}/api/v1/signals/{id}/refine-location3–29 tokens, 0 if it does not improveMost 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
| Parameter | Type | Description |
|---|---|---|
| id | integer (required) | The signal (global_event_id) whose location should be researched further. |
curl -X POST -H "Authorization: Bearer ond_..." \
"https://offnadir-delta.com/api/v1/signals/1318224420/refine-location"{
"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 }
}
}/api/v1/index-series0.5 tokens / scene measuredThe 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
| Parameter | Type | Description |
|---|---|---|
| polygon | array[] | WGS84 ring [[lon, lat], …] with at least 3 vertices. Statistics cover the samples INSIDE it. A GeoJSON Polygon or Feature is also accepted. |
| bbox | number[4] | Alternative to polygon: [lon_min, lat_min, lon_max, lat_max] (WGS84). |
| index | string (required) | Which optical index to measure: ndvi, evi, savi, ndmi, ndwi, mndwi, ndbi, ndsi, nbr, iron-oxide, clay, ferrous. |
| start | string (date) (required) | Start date YYYY-MM-DD (UTC). Clamped forward to 2015-06-27 if earlier. |
| end | string (date) (required) | End date YYYY-MM-DD (UTC). |
| max_scenes | integer 1–24 | Cap on scenes measured in this call. Server maximum 24. |
| max_cloud_cover | number 1–100 | Scene cloud-cover ceiling in percent. Default 30. |
| estimate_only | boolean | True = free: return the scene count, date span and token cost WITHOUT measuring or charging. |
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"{
"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
}/api/v1/ships5 tokens / detectionRadar 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
| Parameter | Type | Description |
|---|---|---|
| collection | string (required) | Catalog collection of the scene. |
| item_id | string (required) | STAC item id of the scene, as returned by GET /api/v1/imagery. |
| bbox | number[4] | Area to search within the scene, [lon_min, lat_min, lon_max, lat_max] (WGS84). Must be under 120,000 km². |
| geometry | object | Alternative to bbox: a GeoJSON Polygon or MultiPolygon. |
| algorithm_version | string | Detector version. 'auto' picks the recommended version for the sensor. |
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"{
"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).
/api/v1/brieffreeThe 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
| Parameter | Type | Description |
|---|---|---|
| date | string (date) | Brief date YYYY-MM-DD (UTC). Defaults to the latest available. |
curl -H "Authorization: Bearer ond_..." "https://offnadir-delta.com/api/v1/brief"{
"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.
/api/v1/assess5/15 tok · AIAI 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
| Parameter | Type | Description |
|---|---|---|
| eventId | integer (required) | Signal id (global_event_id) from /api/v1/signals. |
| kind | string | Assessment depth. quick=5 tok, deep=15 tok. Defaults to quick. |
curl -X POST -H "Authorization: Bearer ond_..." \
-H "Content-Type: application/json" \
-d '{"eventId": 1312473707, "kind": "quick"}' \
"https://offnadir-delta.com/api/v1/assess"{
"kind": "quick",
"cached": false,
"model": "…",
"content": {
"summary": "…",
"observability": "…",
"recommendedSensors": ["SAR satellites", "high-resolution optical"]
},
"meta": { "tokens": { "charged": 5, "remaining": 195 } }
}/api/v1/analyst5–123 tok · AIAsk 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
| Parameter | Type | Description |
|---|---|---|
| Idempotency-Key | string | At-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. |
| question | string (required) | The analytic question (≤ 500 chars). |
| bbox | number[4] | Optional focus bounding box [minLon, minLat, maxLon, maxLat] (WGS84). |
| mode | string | fast (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. |
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"{
"brief": {
"summary": "…",
"findings": [ { "global_event_id": 131…, "recommended_sensor": "SAR", "rationale": "…" } ],
"assessment": "…",
"citations": [ "https://…" ]
},
"meta": { "tokens": { "charged": 12, "remaining": 150 } }
}/api/v1/analyst/{jobId}freePoll 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
| Parameter | Type | Description |
|---|---|---|
| jobId | string (uuid) (required) | The job_id returned by POST /api/v1/analyst. |
curl -H "Authorization: Bearer ond_..." \
"https://offnadir-delta.com/api/v1/analyst/c27a5d66-64a6-4785-aa9a-ea182e23efa6"{
"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.
/api/v1/claimsfreeWhat 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
| Parameter | Type | Description |
|---|---|---|
| since | string | Only claims asserted on or after this date (YYYY-MM-DD or ISO 8601). |
| evidence_class | string | Restrict to one evidence class. |
| restated_only | boolean | Only claims that sit in a restatement chain. |
| downgraded_only | boolean | Only claims a later answer restated with WEAKER evidence — read these first. |
| limit | integer 1–200 | How many claims to return (1..200, default 50). |
curl -H "Authorization: Bearer ond_..." "https://offnadir-delta.com/api/v1/claims?downgraded_only=true"{
"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.
/api/v1/standing-ordersfreeWhat 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.
curl -H "Authorization: Bearer ond_..." "https://offnadir-delta.com/api/v1/standing-orders"{
"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 }
}/api/v1/standing-ordersfree to create · metered per fireTurn 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
| Parameter | Type | Description |
|---|---|---|
| bbox | number[4] (required) | Area to watch, [minLon, minLat, maxLon, maxLat] (WGS84). Required — a global watch would fire on everything. |
| question | string | The question to re-answer when something changes (≤ 500 chars). Defaults to "what changed in this area, and what does it mean?". |
| name | string | Label for the order. |
| cadence | string | How often to CHECK — checking is free, only a firing check costs tokens. Default weekly. |
| categories | string[] | Restrict the watch to these Delta categories. |
| min_geoint_score | number 0–10 | Reporting bar 0–10 (default 6). Raise it to hear only about major developments. |
| min_new_events | number 1–50 | How many new qualifying events must appear before a run is triggered (default 1). |
| notify_email | boolean | Email the result when it fires (default true). Results are readable from the list either way. |
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"{
"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
}/api/v1/standing-orders/{orderId}freeStop watching. Deleting frees the plan slot; pausing (POST with active:false) keeps the order and its history but not the slot.
Parameters
| Parameter | Type | Description |
|---|---|---|
| orderId | string (uuid) (required) | The id returned when the order was created. |
curl -X DELETE -H "Authorization: Bearer ond_..." \
"https://offnadir-delta.com/api/v1/standing-orders/8f3c1a2b-…"{ "deleted": "8f3c1a2b-…" }/api/v1/standing-orders/{orderId}freePause 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
| Parameter | Type | Description |
|---|---|---|
| orderId | string (uuid) (required) | The id returned when the order was created. |
| active | boolean (required) | false = pause, true = resume. |
curl -X POST -H "Authorization: Bearer ond_..." \
-H "Content-Type: application/json" -d '{"active": false}' \
"https://offnadir-delta.com/api/v1/standing-orders/8f3c1a2b-…"{ "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.
/api/v1/monitoringfreeWhat 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.
curl -H "Authorization: Bearer ond_..." "https://offnadir-delta.com/api/v1/monitoring"{
"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 }
}/api/v1/monitoringfree to create · metered per sceneTurn 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
| Parameter | Type | Description |
|---|---|---|
| bbox | number[4] (required) | Area to measure, [minLon, minLat, maxLon, maxLat] (WGS84). Must be under 5,000 km². |
| metric | string (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). |
| name | string | Label for the area. |
| start_date | string (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. |
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"{
"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 }
}/api/v1/monitoring/{areaId}freeThe 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
| Parameter | Type | Description |
|---|---|---|
| areaId | string (uuid) (required) | The area_id from the list endpoint (a metric polygon_id also resolves). |
curl -H "Authorization: Bearer ond_..." "https://offnadir-delta.com/api/v1/monitoring/2b5f1a2b-…"{
"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.
/api/v1/usagefreeYour key’s current token balance and plan capabilities — call it to pre-flight a metered request without spending anything.
curl -H "Authorization: Bearer ond_..." "https://offnadir-delta.com/api/v1/usage"{
"tokens": { "allocation": 10000, "used": 42, "remaining": 9958 },
"plan": { "api_llm_access": true }
}/api/v1/statusfreePre-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.
curl -H "Authorization: Bearer ond_..." "https://offnadir-delta.com/api/v1/status"{
"pipeline_status": "ok",
"data_current_through": "2026-08-14T06:00:00Z",
"server": { "version": "1.11.0", "tool_count": 24, "environment": "production" }
}/api/v1/versionfree · no key neededThe 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.
curl "https://offnadir-delta.com/api/v1/version"{
"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.
https://offnadir-delta.com/api/v1/mcpClaude Code
claude mcp add --transport http off-nadir-delta \
https://offnadir-delta.com/api/v1/mcp \
--header "Authorization: Bearer ond_..."Config file (API key)
{
"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.jsonFrom headline to satellite evidence
One connected intelligence workflow across four surfaces — free to start, no GIS software or remote-sensing background required.