Off-Nadir Delta
Developer reference

REST API reference

Every endpoint under /api/v1, with parameters and examples. New here? Start with the overview and quickstart; try any read endpoint in the Playground; the Python SDK wraps all of them; for AI agents see MCP & AI agents.

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. What a request returns follows your plan: a filter, time window or detail your plan does not include is reported in the response (meta.filter_clamp, meta.window_clamp, plan_lock) rather than applied silently.

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

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": "start_date and end_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
410Endpoint retired (the body names its replacement)
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.
start_datestring (date)Date range start, YYYY-MM-DD (UTC) — the Watchfloor date range. Give with end_date, or neither for the recent window. The event record starts on 2026-09-23: a range before that date returns no events because none were recorded, not because none happened. The range is bounded by the key owner's plan — without real-time it ends yesterday, without the date range it ends on the latest readable day, and it never starts before the plan's history floor; meta.window_clamp says what was narrowed.
end_datestring (date)Date range end, YYYY-MM-DD (UTC), inclusive.
recencystringThe Watchfloor time window: last reported in the last 15 min / hour / 24 hours. Default 24h when no date range is given. 15m and 1h need real-time; without it the 24 hours to the end of the latest readable day are used and meta.filter_clamp says so.
categoriesstringComma-separated category filter — the Watchfloor Category. Omit for all. Six categories are events made from a measurement or an official record, not from reporting: thermal_anomaly (heat sources measured by satellite), transit_anomaly (ship transits or port calls off their own recent level), nightlight_anomaly (night lights over a city below half of its own usual level), so2_anomaly (sulfur dioxide above background around a volcano with an ash advisory), quake_exposure (an earthquake with registered facilities within a set distance — distances, not shaking or damage) and cyclone_exposure (registered places inside the forecast tropical-storm-force wind radii of an Atlantic or eastern Pacific cyclone — a forecast, not an observation). They carry measured=true and measured_kind, state what was recorded and never a cause.
qstringThe Watchfloor search: title, summary, place, country and category. Every word must match; "two words" keeps the order; -word excludes. At most 300 characters.
stagesstringComma-separated location stages (reported, localized, pinpointed): reported = country or province only, localized = town or city, pinpointed = street block or facility.
min_severity_bandintegerThe Watchfloor Severity filter: severity_band at least this (4 / 6 / 9).
min_publishersintegerThe Watchfloor Publishers reporting filter (2 / 3 / 5). Breadth of reporting, not independent confirmation.
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.
placementstringThe Watchfloor Location filter: all (default), map (precise enough to draw as a point) or list (placed only to a country or province). With a bbox, `signals` holds only events with a point inside it; events without a point whose country overlaps the bbox come separately in `country_level_signals` (the first 10, counted in meta.country_level) — placement=list returns all of those, paged. Without a bbox, list and all cover the whole world.
linked_tostringThe Watchfloor "Filter to these events" on a connector: only events that share it. One stored connector the events share: facility:<entity_id> (from search_entities or get_related_events), place:<place_key> (from get_related_events) or actor:<name> (a name in a signal's actors, case-insensitive). Needs a plan that includes it (meta.filter_clamp.required_plans names the plan when it is dropped).
watch_idstring (uuid)The Watchfloor "Near my places" for one place: only events linked to this watch (id from GET /api/v1/watches) — the same up-to-100 most recent events the Watchlist shows for that place, area or event. Another account’s watch, or an unknown id, is 404. meta.watch_linked_events says how many events the watch links; the other filters and the window still apply.
sortstringThe Watchfloor Sort: latest (default), oldest, or geoint (Imaging value: a satellite could see it first, then the GEOINT score, then how finely the place is known). geoint, like the GEOINT score itself, needs a plan that includes it; without it latest is used and meta.filter_clamp says so.
updated_sincestring (ISO 8601)Differential fetch: only signals refolded at/after this ISO 8601 timestamp. Narrows the window rather than replacing it; signals carry last_updated_at.
limitinteger 1–500Max rows per page (1-500, default 100).
cursorstringOpaque cursor from meta.next_cursor. Bound to the query it came from.
Request
curl -H "Authorization: Bearer ond_..." \
  "https://offnadir-delta.com/api/v1/signals?recency=24h&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": "8f2a1c44-9b3e-4d1a-8f6c-2f0b7d9e5a31",
      "category": "armed_conflict",
      "title": "…",
      "summary": "…",
      "occurred_at": "2026-07-08T02:40:00Z",
      "occurred_at_precision": "hour",
      "occurred_at_basis": "stated",
      "last_reported_at": "2026-07-08T06:12:00Z",
      "location": "Kyiv, Ukraine",
      "location_level": "locality",
      "country": "Ukraine",
      "country_code": "UA",
      "lat": 50.45, "lng": 30.52,
      "plottable": true,
      "stage": "localized",
      "stage_reason": "no_finer_place_named",
      "severity_score": 7.8,
      "geoint_score": 6,
      "escalation_trend": "escalating",
      "cross_border_risk": true,
      "infrastructure_target": "energy",
      "casualties": { "killed": null, "injured": 3, "basis": "reported", "disputed": false },
      "reporting": { "member_count": 42, "distinct_hosts": 11,
                     "source_reliability_best": "B", "credibility_best": 2, "graded_members": 42 },
      "collection": {
        "level": "GSD<1m", "sensor": "sentinel-1-sar",
        "target": "…", "reason": "…",
        "niirs": "…", "analysis": "…",
        "aoi": [30.4, 50.4, 30.6, 50.5],
        "gap": null, "observability": 0.82
      },
      "imagery": { "post_status": "available", "sar_pair_status": "pair_available", "latest_scene_date": "2026-07-09" },
      "market_impact": ["oil"],
      "quality": { "status": "passed", "score": 100, "issues": null },
      "last_updated_at": "2026-07-08T06:20:00Z",
      "assessed_at": "2026-07-08T06:21:00Z"
    }
  ]
}
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 it has moved up the evidence ladder since. An id folded into another event resolves to that event.

Parameters

ParameterTypeDescription
idstring (uuid) (required)The signal id (UUID) from a /api/v1/signals result.
Request
curl -H "Authorization: Bearer ond_..." "https://offnadir-delta.com/api/v1/signals/8f2a1c44-9b3e-4d1a-8f6c-2f0b7d9e5a31"
Response
{
  "signal": { "id": "8f2a1c44-9b3e-4d1a-8f6c-2f0b7d9e5a31", "category": "armed_conflict",
              "severity_score": 7.4, "stage": "pinpointed",
              "stage_reason": "located_to_facility",
              "reporting": { "member_count": 9, "distinct_hosts": 4,
                             "source_reliability_best": "B", "credibility_best": 2, "graded_members": 9 } },
  "meta": { "tokens": { "charged": 1 },
            "event_id": "8f2a1c44-9b3e-4d1a-8f6c-2f0b7d9e5a31",
            "merged_from_request": false, "retracted_at": null }
}
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.
start_datestring (date)Date range start (YYYY-MM-DD) — the app's date range. Give with end_date, or neither for today. Changes are recorded from 2026-09-23, when the event record starts. The plan's history window bounds it.
end_datestring (date)Date range end (YYYY-MM-DD), inclusive.
categoriesstringComma-separated signal categories.
axesstringComma-separated change axes (casualties_killed, attributed_actor, stage, location_level, ...). An unknown value is a 400, never an empty result — a silent zero reads as "nothing happened".
notable_onlybooleanDefault true. False returns every recorded change, including minor ones.
include_member_countbooleanDefault false. The member_count axis means "one more report arrived", which says nothing new about the event — including it puts every followed-up story in today’s changes.
limitinteger 1–200How many developments to return (1..200, default 50).
offsetintegerSkip this many developments (meta.total_count is the whole window).
Request
curl -H "Authorization: Bearer ond_..." "https://offnadir-delta.com/api/v1/developments?bbox=22,44,40,53&start_date=2026-09-21&end_date=2026-09-27"
Response
{
  "developments": [
    { "event_id": "3f0c2a1e-5b7d-4c9e-8a21-6d4f0b9c2e17", "axis": "casualties_killed", "source": "world",
      "previous_state": "5", "current_state": "25", "basis": "reported_count", "notable": true,
      "observed_at": "2026-08-22T09:15:00Z", "title": "Strike on a fuel depot", "stage": "pinpointed" },
    { "event_id": "91ab4d0c-2e6f-4b8a-9c13-7f5e2d8a4b60", "axis": "imagery_post_status", "source": "measurement",
      "previous_state": "none", "current_state": "confirmed_post", "basis": "imagery_searched_confirmed_post", "notable": true,
      "observed_at": "2026-08-22T11:02:00Z", "title": "Flooding along the river", "stage": "localized" }
  ],
  "meta": { "events_examined": 214, "count": 2, "total_count": 2,
            "by_axis": { "casualties_killed": 1, "imagery_post_status": 1 }, "by_source": { "world": 1, "measurement": 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
eventIdstring (uuid) (required)The event id (UUID). An id folded into another event resolves to that event, and merged_from_request says so.
Request
curl -H "Authorization: Bearer ond_..." "https://offnadir-delta.com/api/v1/events/3f0c2a1e-5b7d-4c9e-8a21-6d4f0b9c2e17/thread"
Response
{
  "canonical_event": {
    "event_id": "3f0c2a1e-5b7d-4c9e-8a21-6d4f0b9c2e17", "merged_from_request": false, "retracted": false,
    "title": "Red Fort explosion", "location": "Red Fort, Delhi", "location_level": "facility",
    "stage": "pinpointed", "stage_reason": "located_to_facility",
    "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, "injured": null, "basis": "reported_count", "disputed": false, "not_counted": [] },
    "occurrence": { "status": "reported", "claimed_by": [], "denied_by": [], "not_confirmed_by": null, "observed": [] },
    "attributed_actor": "AQIS", "attribution_basis": "attributed_by_official",
    "reporting": { "member_count": 9, "distinct_hosts": 6, "distinct_texts": 5 }
  },
  "timeline": [
    { "at": "2026-08-21T06:10:00Z", "axis": "attributed_actor", "source": "world",
      "previous_state": null, "current_state": "AQIS", "basis": "attributed_by_official", "notable": true }
  ],
  "timeline_total": 1,
  "sources": [{ "host": "example-news.com", "source_name": null,
                "documents": [{ "document_id": "…", "url": "https://…", "title": "…" }] }]
}
GET/api/v1/thermal-anomalies3 tok / page

Heat sources far above their own recent level, measured by satellite — near registered facilities, and strong isolated ones at night away from any. A measurement, not a report — it says what was detected and never why. Each one is also an event, linked to reporting on the same facility when there is any.

Parameters

ParameterTypeDescription
bboxstringBounding box "minLon,minLat,maxLon,maxLat" (WGS84). Omit for worldwide.
start_datestring (date)Date range start, YYYY-MM-DD (UTC). Give with end_date, or neither for the last 7 days up to the latest day your plan can read.
end_datestring (date)Date range end, YYYY-MM-DD (UTC), inclusive.
limitinteger 1–100Max rows per page (1-100, default 50).
cursorstringOpaque cursor from meta.next_cursor. Bound to the query it came from.
Request
curl -H "Authorization: Bearer ond_..." \
  "https://offnadir-delta.com/api/v1/thermal-anomalies?start_date=2026-10-01&end_date=2026-10-06"
Response
{
  "meta": { "start_date": "2026-10-01", "end_date": "2026-10-06", "count": 1, "total_count": 1,
            "definition": { "min_frp_mw": 10, "times_baseline": 5, "baseline_days": 21,
                            "baseline_radius_m": 500, "facility_radius_m": 2000 } },
  "thermal_anomalies": [
    { "event_id": "74f7…", "kind": "facility", "title": "Anomalous heat source detected at <facility>",
      "detected_at": "2026-10-02T00:35:00+00:00",
      "location": { "lat": 48.4952, "lng": 44.6236 },
      "facility": { "entity_id": "ee40…", "name": "<facility>", "subtype": "refinery_energy", "country": "RU" },
      "distance_m": 226, "frp_mw": 20.76, "baseline_max_mw": 0.62, "times_baseline": 33,
      "satellite": "NOAA-20", "satellites": ["Suomi NPP", "NOAA-20"], "confidence": "nominal", "overpass": "night",
      "severity": { "score": null, "band": null },
      "linked_events": [] }
  ]
}
GET/api/v1/transit-anomalies3 tok / request

Whether ship transits through each of 28 straits and canals are within their usual range, as an index against the usual level (100) — and, for the ones that are not, since when and the event recorded for it. A measurement, not a report — it says the number of ships changed and never why. Updated weekly, so a change shows 4 to 11 days after it happens.

Request
curl -H "Authorization: Bearer ond_..." "https://offnadir-delta.com/api/v1/transit-anomalies"
Response
{
  "meta": { "as_of": "2026-09-28", "count": 28, "anomalous_count": 1, "detail_included": true,
            "definition": "Usual level: the median of the 7-day averages over the 12 weeks before. …",
            "attribution": "Sources: UN Global Platform; IMF PortWatch (portwatch.imf.org)" },
  "places": [
    { "entity_id": "5c1e…", "name": "<strait>", "lat": 26.6, "lng": 56.5, "status": "below",
      "level": { "as_of": "2026-09-28", "status": "below", "index": 57, "change": -0.427,
                 "tanker_index": 56, "revised": false, "first_recorded_at": "2026-10-02T06:00:00+00:00" },
      "detail_withheld": false,
      "anomaly": { "direction": "below", "since": "2026-09-19", "days": 10, "event_id": "9a3d…" } },
    { "entity_id": "0b7f…", "name": "<canal>", "lat": 30.6, "lng": 32.3, "status": "usual",
      "level": { "as_of": "2026-09-28", "status": "usual", "index": 98, "change": -0.021,
                 "tanker_index": 103, "revised": false, "first_recorded_at": "2026-10-02T06:00:00+00:00" },
      "detail_withheld": false, "anomaly": null }
  ]
}
GET/api/v1/nightlight-anomalies3 tok / page

Nights on which the satellite-measured lights over a city fell below half of the city's own usual level. A measurement, not a report — it says how far the lights fell and never why. Only a fixed list of cities is measured, and a cloudy night is not judged.

Parameters

ParameterTypeDescription
bboxstringBounding box "minLon,minLat,maxLon,maxLat" (WGS84). Omit for worldwide.
start_datestring (date)Date range start, YYYY-MM-DD (UTC). Give with end_date, or neither for the last 7 days up to the latest day your plan can read.
end_datestring (date)Date range end, YYYY-MM-DD (UTC), inclusive.
limitinteger 1–100Max rows per page (1-100, default 50).
cursorstringOpaque cursor from meta.next_cursor. Bound to the query it came from.
Request
curl -H "Authorization: Bearer ond_..." \
  "https://offnadir-delta.com/api/v1/nightlight-anomalies?start_date=2026-10-01&end_date=2026-10-07"
Response
{
  "meta": { "measured_kind": "nightlight", "start_date": "2026-10-01", "end_date": "2026-10-07", "count": 1, "total_count": 1,
            "detail_included": true, "definition": {"ratio_below":0.5,"min_observed_share":0.7,"min_baseline_nights":5,"max_baseline_nights":30,"episode_gap_nights":3} },
  "nightlight_anomalies": [
    { "event_id": "3b1c…", "measured_kind": "nightlight", "category": "nightlight_anomaly", "detail_withheld": false,
      "title": "Night lights over <city> at 41% of their usual level", "recorded_at": "2026-10-06T23:59:00+00:00",
      "location": { "lat": 33.51, "lng": 36.29 },
      "city": { "slug": "<city>", "name": "<city>", "country_code": "SY", "lat": 33.51, "lng": 36.29, "half_size_deg": 0.15 },
      "night": "2026-10-06", "first_night": "2026-10-05", "nights": ["2026-10-05", "2026-10-06"],
      "ratio_to_usual": 0.41, "percent_of_usual": 41, "observed_share": 0.93, "baseline_nights": 18,
      "product": "VJ146A2_NRT", "granule": "VJ146A2_NRT.A2026279.h21v03.002.…h5", "provisional": true, "source_url": null, "related_events": [] }
  ]
}
GET/api/v1/so2-anomalies3 tok / page

Sulfur dioxide measured by satellite clearly above the local background around a volcano with a recent ash advisory. A measurement, not a report — it gives the column amounts and how many pixels stood out, not the size of an eruption and not whether ash was present.

Parameters

ParameterTypeDescription
bboxstringBounding box "minLon,minLat,maxLon,maxLat" (WGS84). Omit for worldwide.
start_datestring (date)Date range start, YYYY-MM-DD (UTC). Give with end_date, or neither for the last 7 days up to the latest day your plan can read.
end_datestring (date)Date range end, YYYY-MM-DD (UTC), inclusive.
limitinteger 1–100Max rows per page (1-100, default 50).
cursorstringOpaque cursor from meta.next_cursor. Bound to the query it came from.
Request
curl -H "Authorization: Bearer ond_..." \
  "https://offnadir-delta.com/api/v1/so2-anomalies?start_date=2026-10-01&end_date=2026-10-07"
Response
{
  "meta": { "measured_kind": "so2", "start_date": "2026-10-01", "end_date": "2026-10-07", "count": 1, "total_count": 1,
            "detail_included": true, "definition": {"near_deg":0.5,"ring_deg":2,"min_quality":0.5,"sigma":5,"min_du":1,"min_pixels":10,"min_ratio_to_expected":3} },
  "so2_anomalies": [
    { "event_id": "8d2e…", "measured_kind": "so2", "category": "so2_anomaly", "detail_withheld": false,
      "title": "Elevated sulfur dioxide measured over <volcano> volcano", "recorded_at": "2026-10-07T04:59:07+00:00",
      "location": { "lat": 31.6, "lng": 130.65 },
      "volcano": { "name": "<volcano>", "lat": 31.6, "lng": 130.65 }, "sensed_at": "2026-10-07T04:59:07Z",
      "peak_du": 6.26, "background_du": 0.1, "background_spread_du": 0.56, "pixels_over": 35, "pixels_expected": 1.6,
      "pixels_near": 641, "granule": "S5P_NRTI_L2__SO2____20261007T045907_…", "source_url": null, "related_events": [] }
  ]
}
GET/api/v1/quake-exposures3 tok / page

Moderate earthquakes recorded by USGS with a registered facility within a set distance of the epicentre, each with the facilities and how far each is. A seismic record and distances — it does not measure shaking or damage at any facility.

Parameters

ParameterTypeDescription
bboxstringBounding box "minLon,minLat,maxLon,maxLat" (WGS84). Omit for worldwide.
start_datestring (date)Date range start, YYYY-MM-DD (UTC). Give with end_date, or neither for the last 7 days up to the latest day your plan can read.
end_datestring (date)Date range end, YYYY-MM-DD (UTC), inclusive.
limitinteger 1–100Max rows per page (1-100, default 50).
cursorstringOpaque cursor from meta.next_cursor. Bound to the query it came from.
Request
curl -H "Authorization: Bearer ond_..." \
  "https://offnadir-delta.com/api/v1/quake-exposures?start_date=2026-10-01&end_date=2026-10-07"
Response
{
  "meta": { "measured_kind": "quake_exposure", "start_date": "2026-10-01", "end_date": "2026-10-07", "count": 1, "total_count": 1,
            "detail_included": true, "definition": {"radius_by_magnitude":[{"min_magnitude":6.5,"radius_km":100},{"min_magnitude":5.5,"radius_km":50},{"min_magnitude":5,"radius_km":25}],"max_depth_km":70,"max_facilities_listed":5} },
  "quake_exposures": [
    { "event_id": "c41a…", "measured_kind": "quake_exposure", "category": "quake_exposure", "detail_withheld": false,
      "title": "M 5.6 - <USGS place text> — 18 km from <facility>", "recorded_at": "2026-10-06T18:34:31+00:00",
      "location": { "lat": 38.12, "lng": 141.9 },
      "earthquake": { "usgs_id": "us7000…", "magnitude": 5.6, "magnitude_type": "mww", "depth_km": 35.2,
                      "occurred_at": "2026-10-06T18:34:31.760Z", "epicentre": { "lat": 38.12, "lng": 141.9 },
                      "place": "<USGS place text>", "review_status": "reviewed",
                      "url": "https://earthquake.usgs.gov/earthquakes/eventpage/us7000…" },
      "radius_km": 50,
      "facilities": [ { "entity_id": "ee40…", "name": "<facility>", "subtype": "port_facility", "country": "JP", "distance_km": 18.4 } ],
      "facilities_total": 1, "source_url": "https://earthquake.usgs.gov/earthquakes/eventpage/us7000…", "related_events": [] }
  ]
}
GET/api/v1/cyclone-exposures3 tok / page

Active Atlantic, eastern Pacific and central Pacific tropical cyclones whose forecast tropical-storm-force wind radii cover registered facilities or straits, now or within 48 hours. An official forecast applied to registered places — not an observation of wind or damage, and not a warning.

Parameters

ParameterTypeDescription
bboxstringBounding box "minLon,minLat,maxLon,maxLat" (WGS84). Omit for worldwide.
start_datestring (date)Date range start, YYYY-MM-DD (UTC). Give with end_date, or neither for the last 7 days up to the latest day your plan can read.
end_datestring (date)Date range end, YYYY-MM-DD (UTC), inclusive.
limitinteger 1–100Max rows per page (1-100, default 50).
cursorstringOpaque cursor from meta.next_cursor. Bound to the query it came from.
Request
curl -H "Authorization: Bearer ond_..." \
  "https://offnadir-delta.com/api/v1/cyclone-exposures?start_date=2026-10-01&end_date=2026-10-07"
Response
{
  "meta": { "measured_kind": "cyclone_exposure", "start_date": "2026-10-01", "end_date": "2026-10-07", "count": 1, "total_count": 1,
            "detail_included": true, "definition": {"wind_kt":34,"forecast_hours":48,"basins":["Atlantic","eastern Pacific","central Pacific"]} },
  "cyclone_exposures": [
    { "event_id": "a07f…", "measured_kind": "cyclone_exposure", "category": "cyclone_exposure", "detail_withheld": false,
      "title": "Tropical Storm <name>: 2 registered places inside its forecast tropical-storm-force wind radii within 48 hours",
      "recorded_at": "2026-10-07T03:00:00+00:00", "location": { "lat": 22.1, "lng": -95.0 },
      "storm": { "id": "al092026", "name": "<name>", "classification": "TS" },
      "advisory": { "number": "002", "issued_at": "2026-10-07T03:00:00.000Z",
                    "url": "https://www.nhc.noaa.gov/text/MIATCPAT4.shtml", "forecast_url": "https://www.nhc.noaa.gov/text/MIATCMAT4.shtml" },
      "centre": { "lat": 22.1, "lng": -95.0 }, "max_wind_kt": 40, "min_pressure_mb": 1003,
      "wind_radii_34kt_nm": { "NE": 40, "SE": 40, "SW": 20, "NW": 30 },
      "forecast_positions": [ { "valid_at": "2026-10-07T12:00:00.000Z", "lat": 22.2, "lng": -94.2, "max_wind_kt": 45,
                                "wind_radii_34kt_nm": { "NE": 50, "SE": 50, "SW": 30, "NW": 40 } } ],
      "places_inside_now": [], "places_inside_now_count": 0,
      "places_inside_forecast": [ { "entity_id": "7b9c…", "name": "<place>", "subtype": "port_facility", "country": "MX",
                                    "distance_km": 74.3, "forecast_at": "2026-10-08T00:00:00.000Z" } ],
      "places_inside_forecast_count": 2, "source_url": "https://www.nhc.noaa.gov/text/MIATCPAT4.shtml", "related_events": [] }
  ]
}

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.
start_datestring (date)Date range start (YYYY-MM-DD, UTC) — the app's imagery date range. Give with end_date, or neither for the last 7 days. At most 30 days.
end_datestring (date)Date range end (YYYY-MM-DD, UTC), inclusive.
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&start_date=2026-09-14&end_date=2026-09-27&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_idstring (uuid) (required)The signal id (UUID) from /api/v1/signals. An id folded into another event resolves to that event; meta.merged_from_request says so.
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=8f2a1c44-9b3e-4d1a-8f6c-2f0b7d9e5a31&analysis_goal=damage_assessment" \
  -H "Authorization: Bearer ond_..."
Response
{
  "meta": { "event_id": "8f2a1c44-9b3e-4d1a-8f6c-2f0b7d9e5a31", "merged_from_request": false, "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": { "recommended": false, "assessed": false, "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 your plan history floor (and to yesterday without real-time); meta.window_clamp says what was narrowed. The event record starts on 2026-09-23: an earlier window returns no events because none were recorded, not because none happened.
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_priorities": [ { "event_id": "8f2a1c44-9b3e-4d1a-8f6c-2f0b7d9e5a31", "importance": 0.86, "spec_class": "vhr", "aoi": [56.2, 27.1, 56.4, 27.3], "stage": "pinpointed" } ]
  }
}
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 your plan history floor (and to yesterday without real-time); meta.window_clamp says what was narrowed. The event record starts on 2026-09-23: an earlier window returns no events because none were recorded, not because none happened.
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",
    "total_in_window": 32, "examined": 32, "page_capped": false,
    "observable_count": 9,
    "not_observable_count": 23,
    "by_rs_level": { "GSD<10m": 6, "GSD<100m": 3 },
    "collection_ready_count": 7,
    "events": [ { "event_id": "8f2a1c44-9b3e-4d1a-8f6c-2f0b7d9e5a31", "headline": "Strike damages storage tanks at the terminal",
                  "rs_level": "GSD<10m", "rs_target": "damaged structures ~10m", "stage": "pinpointed",
                  "collection_ready": true, "plottable": true } ]
  }
}
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, nisar, worldview, iceye, capella, skysat, umbra, synspective, iqps, radarsat-2, cosmo-skymed. Omit for all thirteen.
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, "is_past": false
    }
  ],
  "freshness": { "max_tle_age_days": 1.2, "stale": false },
  "retrieval_ok": true,
  "note": "Systematic (Sentinel/Landsat) passes are also geometric predictions from orbital elements — the sensor routinely collects, but no published acquisition plan was checked, so they are not a scheduled acquisition.",
  "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/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: Sentinel-1, or NISAR L-band (the granule is converted to a COG first, which costs extra tokens the first time).
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 World Brief — an AI-synthesized digest of worldwide event signals (headline, executive summary, top developments, ranked signals). Daily covers the previous UTC day and is open on every plan; weekly and monthly are included by plan. Free of token charges.

Parameters

ParameterTypeDescription
periodstringdaily (default), weekly or monthly.
datestring (date)YYYY-MM-DD (UTC). For daily, the day the brief covers; for weekly / monthly, the last day of the period (period_end — available_periods lists them). Defaults to the latest available.
Request
curl -H "Authorization: Bearer ond_..." "https://offnadir-delta.com/api/v1/brief?period=weekly"
Response
{
  "status": "available",
  "period": { "type": "weekly", "start": "2026-09-21", "end": "2026-09-27" },
  "brief": {
    "headline": "…",
    "summary": "…",
    "developments": [ { "title": "…", "why_it_matters": "…" } ],
    "stats": { "totalEvents": …, "byCategory": [ … ], "countedOver": "window" }
  },
  "available_periods": [ { "period_start": "2026-09-21", "period_end": "2026-09-27" } ]
}

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. The response also carries a deterministic `context` block whose `imagery_handoff.parameters` are the exact `/api/v1/imagery` inputs for real pre/post scene candidates — pass them through unchanged.

Parameters

ParameterTypeDescription
eventIdstring (uuid) (required)Signal id (UUID) 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": "8f2a1c44-9b3e-4d1a-8f6c-2f0b7d9e5a31", "kind": "quick"}' \
  "https://offnadir-delta.com/api/v1/assess"
Response
{
  "kind": "quick",
  "cached": false,
  "model": "…",
  "content": {
    "summary": "…",
    "observability": "…",
    "recommendedSensors": ["SAR satellites", "high-resolution optical"]
  },
  "context": {
    "event_id": "8f2a1c44-9b3e-4d1a-8f6c-2f0b7d9e5a31",
    "event_date": "2026-07-24",
    "target": "port terminal",
    "aoi_bbox": [30.72, 46.46, 30.79, 46.52],
    "satellite_observability": "observable",
    "quality_status": "passed",
    "imagery_handoff": {
      "tool": "search_imagery",
      "handoff_mode": "targeted_collection",
      "parameters": {
        "bbox": [30.72, 46.46, 30.79, 46.52],
        "eventDate": "2026-07-24",
        "eventPoint": [30.75, 46.49],
        "eventAoi": [30.72, 46.46, 30.79, 46.52],
        "eventTimestamp": null
      },
      "missing_parameters": [
        { "field": "eventTimestamp", "reason": "Only the event date is available; same-day scenes cannot be ordered pre/post without a wall-clock time." }
      ]
    }
  },
  "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": [ { "event_id": "8f2a1c44-9b3e-4d1a-8f6c-2f0b7d9e5a31", "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"
}

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

Watch an area for new events, the same as the app’s "Watch this area". A deterministic pass over the corpus decides whether anything crossed the reporting bar and sends a digest of what did; no model is run and nothing is charged.

Parameters

ParameterTypeDescription
bboxnumber[4] (required)Area to watch, [minLon, minLat, maxLon, maxLat] (WGS84). Required, and at most 100,000 km² — a larger box returns 400 invalid_input (to follow a whole country, create a watch with target_type "site" and scale "country").
namestringLabel for the order.
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]}' \
  "https://offnadir-delta.com/api/v1/standing-orders"
Response
{
  "order": { "id": "8f3c…", "name": "Kyiv oblast", "cadence": "weekly", "next_due_at": "2026-09-28T09:20:00Z" },
  "watch_id": "c1d2…"
}

Monitoring

Watchlist measurements: 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.
satellitestringThe sensor to measure with, chosen the way the app’s monitoring form does it (satellite × metric) — e.g. ship detection on Sentinel-1 or on NISAR. Omit for the metric’s first sensor (ship detection: Sentinel-1).
start_datestring (date)YYYY-MM-DD to begin the history from (default 3 months ago, as in the app). 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_…" } ] }] }
}

Locations

The location registry — ports, bases, plants, chokepoints and named seas — and what has happened at each. Every event link carries how it was established (the report named the place, or it was geolocated within range) so an association is never mistaken for a location claim. Reading is free; there is no bulk export.

GET/api/v1/entities/{entityId}1 token / lookup

The history of a place, with the provenance of every link attached. This is what makes a facility page auditable rather than asserted.

Parameters

ParameterTypeDescription
entityIdstring (uuid) (required)The registry id.
Request
curl -H "Authorization: Bearer ond_..." "https://offnadir-delta.com/api/v1/entities/c997ba41-227d-4064-89f6-253da0edb757"
Response
{
  "entity": { "canonical": "Strait of Hormuz", "subtype": "chokepoint", "source": "curated" },
  "events": [
    { "event_id": "32ec7afe-9d43-4fcc-a285-06d5e1463818",
      "title": "60 ships passed through Strait of Hormuz in one day",
      "stage": "reported", "last_reported_at": "2026-09-24T21:00:00+00:00",
      "link_basis": "alias_named_target", "link_kind": "named_target",
      "confidence": 0.75, "distance_m": null }
  ],
  "event_count": 8, "events_24h": 8
}
GET/api/v1/referenceFree · Starter and above

Who or what a name in the reporting is, with the source and licence attached. Quoted, not assessed — and an absent article is reported as absent, not as unimportant.

Parameters

ParameterTypeDescription
namestring (required)The name as written in the event, 2–160 characters.
entity_idstring (uuid)For a facility, the registry id — a same-named article far from it is rejected.
Request
curl -H "Authorization: Bearer ond_..." "https://offnadir-delta.com/api/v1/reference?name=Ras%20Tanura"
Response
{
  "query": { "name": "Ras Tanura" },
  "basis": "stored",
  "profile": {
    "title": "Ras Tanura",
    "short_description": "Governorate in Eastern Province, Saudi Arabia",
    "extract": "Ras Tanura, also colloquially known as Rahima, is a governorate in the Eastern Province of Saudi Arabia. …",
    "page_url": "https://en.wikipedia.org/wiki/Ras_Tanura",
    "image": {
      "url": "https://upload.wikimedia.org/wikipedia/commons/3/39/DammamFromTheISS.jpg",
      "license": "Public domain",
      "credit": "NASA/Chris Hadfield",
      "file_page": "https://en.wikipedia.org/wiki/File%3ADammamFromTheISS.jpg"
    },
    "facts": [{ "label": "Type", "value": "Governorate" }],
    "lat": 26.63333333,
    "lng": 50.15,
    "source": "wikipedia",
    "fetched_at": "2026-09-26T07:51:50.785Z"
  },
  "plan_lock": null,
  "attribution": "Text: Wikipedia contributors, CC BY-SA 4.0. Images: licence per file, as stated."
}

Watchlist

The aggregation layer over everything under watch. A watch is a target (an area or a real-world event) plus its capabilities — measurements, standing orders, event tracking — grouped as one object with a state bucket. Monitoring and standing orders created anywhere join the Watchlist automatically.

GET/api/v1/watchesfree

What changed in what you care about — the one call that answers it. Buckets are meaningful-change based: a new measurement point alone is not a change; an anomaly, a notable development, or a fired standing order is.

Parameters

ParameterTypeDescription
updated_sincestring (ISO 8601)ISO 8601 instant. Return only watches whose content changed after it. This tracks every table that contributes to a watch (measurements, notes, notifications, bindings), not just edits to the watch row, so a quiet result really means nothing changed rather than nothing was edited.
cursorstringOpaque pagination cursor from a previous response's meta.next_cursor. Bound to the query that produced it — a cursor from a different updated_since is rejected rather than silently returning overlapping rows.
limitinteger 1–500Watches per page when paging (1..500, default 100).
Request
curl -H "Authorization: Bearer ond_..." "https://offnadir-delta.com/api/v1/watches"
Response
{
  "watches": [
    { "id": "3f1b…", "name": "Strait of Hormuz", "target_type": "area", "status": "active",
      "bucket": "needs_attention", "area": { "bbox": [55.8, 25.8, 57.2, 27.2], "area_status": "anomaly" },
      "measurements": [{ "metric": "ship_detection", "latest": { "value": 31, "is_anomaly": true } }],
      "questions": [{ "question": "Is shipping being disrupted?", "cadence": "daily" }],
      "latest_change": { "at": "2026-08-27T06:10:00Z", "summary": "Anomaly in Ship count" } }
  ],
  "buckets": { "needs_attention": 1, "changed_today": 0, "awaiting_data": 0, "stable": 2 },
  "limits": { "max_active": 25, "used_active": 3, "remaining": 22 }
}
POST/api/v1/watchesfree

Watch a real-world event, not its coverage: the binding is the canonical event, so the watch survives new reporting and cluster merges. Event watches track state already computed for everyone — they never charge.

Parameters

ParameterTypeDescription
target_typestring (required)"event", "area" or "site".
event_idstring (uuid)The signal’s id (uuid; required for "event").
bboxnumber[4][minLon, minLat, maxLon, maxLat] WGS84 (required for "area"), at most 100,000 km² — a larger box returns 400 invalid_input when creating (re-adding an existing area is allowed).
entity_idstring (uuid)Site: a registry facility id (from GET /api/v1/entities/search). Its coordinates are used; lat/lon are ignored.
latnumber -90–90Site: latitude of any point (when no entity_id).
lonnumber -180–180Site: longitude of any point (when no entity_id).
scalestringSite: how wide to look — "facility" (default), "city" or "country".
radius_mintegerSite: radius in metres, snapped to the scale's steps (facility 250/500/1000/2000/3000; city 5000/10000/20000). Defaults from the kind of facility.
country_codestringSite, country scale: ISO 3166-1 alpha-2. Resolved from the point when omitted.
metricstringSite: what to measure from imagery (a monitoring metric such as "ship_detection", "vv", "ndvi"), or "none". Omit for the suggested measurement.
measurebooleanSite: false to follow reporting only (same as metric "none").
namestringLabel for the watch.
notify_emailbooleanEmail on meaningful changes (default false).
imagery_alertsbooleanArea and facility- or city-scale site watches: notify when a NEW scene covering this area is published (Sentinel-2 optical and Sentinel-1 SAR). Default false. It reports that a look is now possible — never what is in the scene. Free on every plan: no tokens, no LLM.
Request
curl -X POST -H "Authorization: Bearer ond_..." \
  -H "Content-Type: application/json" \
  -d '{"target_type": "event", "event_id": "8f2a1c44-9b3e-4d1a-8f6c-2f0b7d9e5a31", "notify_email": true}' \
  "https://offnadir-delta.com/api/v1/watches"
Response
{
  "watch": { "id": "3f1b…", "name": "Odesa Port strike", "target_type": "event", "status": "active",
             "bucket": "awaiting_data", "event": { "id": "8f2a1c44-…", "stage": "reported" } },
  "already_existed": false
}
GET/api/v1/watches/{watchId}free

The single-call state readout: built so an agent does not need a chain of follow-ups to answer "what is the state of what I watch".

Parameters

ParameterTypeDescription
watchIdstring (required)The watch id.
include_passesbooleanAdd the collection outlook: which operators can image this target, when, at what off-nadir geometry, whether a commercial tasking order is needed and where to place it. Costs the same as GET /api/v1/passes because it is the same answer; omit it and the call stays free. Off-Nadir Delta does not broker tasking.
Request
curl -H "Authorization: Bearer ond_..." "https://offnadir-delta.com/api/v1/watches/3f1b2a00-…"
Response
{
  "watch": { "id": "3f1b…", "target_type": "event", "bucket": "changed_today",
             "event": { "stage": "corroborated", "developments_24h": 2,
                        "imagery_post_status": "post_available" } },
  "thread": { "canonical_event": { "title": "Odesa Port strike" }, "timeline": [ "…" ], "sources": [ "…" ] }
}
PATCH/api/v1/watches/{watchId}free

Pause propagates: a paused watch is not a display state over live billing — the bound measurements and checks actually stop.

Parameters

ParameterTypeDescription
watchIdstring (required)The watch id.
statusstring"active", "paused", "saved" or "closed". Closing requires close_reason.
close_reasonstringRequired when status is "closed": "resolved" (an answer was reached), "lapsed" (interest moved on without an answer) or "false_alarm" (never a watchable event). They point at different things to fix, so they are not collapsed into one.
notify_emailbooleanEmail on meaningful changes.
notify_chatbooleanAlso post this watch’s notifications to your connected Slack or Discord channel (default false). The channel is connected once, in account settings; while none is connected the setting is kept and nothing is posted. See GET /api/v1/notification-channel.
imagery_alertsbooleanArea watches only: notify when a NEW scene covering this area is published (Sentinel-2 optical and Sentinel-1 SAR). Default false. It reports that a look is now possible — never what is in the scene. Free on every plan: no tokens, no LLM.
Request
curl -X PATCH -H "Authorization: Bearer ond_..." \
  -H "Content-Type: application/json" \
  -d '{"status": "paused"}' \
  "https://offnadir-delta.com/api/v1/watches/3f1b2a00-…"
Response
{ "watch": { "id": "3f1b…", "status": "paused" } }
DELETE/api/v1/watches/{watchId}free

The destructive one, and it says so: bound monitoring and standing orders go with the watch. Pause instead to keep the history.

Parameters

ParameterTypeDescription
watchIdstring (required)The watch id.
Request
curl -X DELETE -H "Authorization: Bearer ond_..." "https://offnadir-delta.com/api/v1/watches/3f1b2a00-…"
Response
{ "deleted": "3f1b…", "removed": { "monitoring_areas": 1, "standing_orders": 1 } }
POST/api/v1/watches/{watchId}/notesfree

A note is just text. Add a confidence and it becomes a judgment — confidence and likelihood are separate axes on purpose: how sure you are, and how probable the thing is, are different statements.

Parameters

ParameterTypeDescription
watchIdstring (required)The watch id.
notestring (required)What to keep with this watch.
titlestringA heading makes it a thread others can reply to. A reply cannot have one.
parent_idstringReply to this note id. One level deep.
confidencestringHow sure the judgment is — about the evidence, not the event. Omit it and the entry is a plain note.
likelihoodstringHow probable the thing itself is (ICD 203). Omit rather than guess.
gapsstring[]What would change this judgment.
next_checkstringWhen or what to look at next.
Request
curl -X POST -H "Authorization: Bearer ond_..." \
  -H "Content-Type: application/json" \
  -d '{"note": "Port authority notice gives a different figure — watch whether the reporting converges."}' \
  "https://offnadir-delta.com/api/v1/watches/3f1b2a00-…/notes"
Response
{
  "note": { "id": "b1c2…", "note": "Port authority notice gives …", "confidence": null,
            "supersedes_id": null, "created_by": "user" }
}
DELETE/api/v1/watches/{watchId}/notes/{noteId}free

The note goes; the deletion is still recorded. A ledger you cannot correct is a ledger nobody writes in.

Parameters

ParameterTypeDescription
watchIdstring (required)The watch id.
noteIdstring (required)The note id.
Request
curl -X DELETE -H "Authorization: Bearer ond_..." \
  "https://offnadir-delta.com/api/v1/watches/3f1b2a00-…/notes/b1c2d3e4-…"
Response
{ "deleted": "b1c2d3e4-…" }
GET/api/v1/notificationsfree

The answer to "what changed?" across every watch, one row per change. A withheld value is marked locked with the plan that opens it — never filled in.

Parameters

ParameterTypeDescription
unread_onlybooleantrue returns only notifications not yet marked read.
kindstringOnly this kind of notification.
watch_idstringOnly notifications from this watch (id from GET /api/v1/watches).
cursorstringOpaque pagination cursor from a previous response's next_cursor.
limitinteger 1–100Notifications per page (1..100, default 20).
Request
curl -H "Authorization: Bearer ond_..." "https://offnadir-delta.com/api/v1/notifications?unread_only=true"
Response
{
  "notifications": [
    { "id": 4812, "kind": "monitoring_anomaly", "kind_label": "Anomaly",
      "line": "Anomaly in Ship count: 31 on 2026-09-28 — departs from this area's own recent history",
      "at": "2026-09-28", "read_at": null,
      "watch": { "id": "3f1b…", "name": "Strait of Hormuz", "target_type": "area", "path": "/watchlist/3f1b…" },
      "change": null, "hosts": [], "locked": false, "lock_plan": null, "event": null, "related_event": null }
  ],
  "unread_count": 3,
  "next_cursor": null
}
POST/api/v1/notifications/readfree

Clears the same unread count the bell in the app shows — reading in one place reads everywhere.

Parameters

ParameterTypeDescription
idsinteger[1]Notification ids to mark read (1–500).
allbooleantrue marks every unread notification read (or every one of watch_id).
watch_idstringOnly notifications from this watch.
Request
curl -X POST -H "Authorization: Bearer ond_..." \
  -H "Content-Type: application/json" \
  -d '{"all": true}' \
  "https://offnadir-delta.com/api/v1/notifications/read"
Response
{ "updated": 3, "unread_count": 0 }
GET/api/v1/notification-channelfree

Where notifications go besides the app and email. The URL that posts to the channel stays on the server — you only see which channel it is and whether it still works.

Request
curl -H "Authorization: Bearer ond_..." \
  "https://offnadir-delta.com/api/v1/notification-channel"
Response
{
  "channel": { "provider": "slack", "channel_label": "#ops", "workspace_label": "Acme",
    "connected_at": "2026-09-30T10:00:00Z", "last_delivered_at": "2026-09-30T11:40:03Z",
    "disabled_at": null, "last_error": null },
  "available_providers": ["slack", "discord"]
}
DELETE/api/v1/notification-channelfree

Stops posting to the channel. Which watches post there is remembered for when you connect again.

Request
curl -X DELETE -H "Authorization: Bearer ond_..." \
  "https://offnadir-delta.com/api/v1/notification-channel"
Response
{ "disconnected": true }
POST/api/v1/notification-channel/testfree

Checks the channel still accepts messages without waiting for the next change.

Request
curl -X POST -H "Authorization: Bearer ond_..." \
  "https://offnadir-delta.com/api/v1/notification-channel/test"
Response
{ "delivered": true, "error": null, "disabled": false }

Workspace

The map assets your account owns — saved layer sets and your own uploaded data. Reading is free. Creating them is map work (arranging layers, transferring a file), so it stays in the app.

GET/api/v1/layer-setsfree

The named map configurations saved on this account, most recently changed first. Includes how many layers each holds, its tags and when it was last opened — enough to tell a working configuration from an abandoned one. The serialized layer tree is internal and not returned.

Parameters

ParameterTypeDescription
limitinteger 1–100Max layer sets to return.
Request
curl -H "Authorization: Bearer ond_..." \
  "https://offnadir-delta.com/api/v1/layer-sets?limit=2"
Response
{
  "layer_sets": [
    { "id": "…", "name": "Hormuz watch", "layer_count": 4, "tags": ["maritime"], "last_loaded_at": "2026-08-20T09:12:00Z" }
  ],
  "total": 7
}
GET/api/v1/layer-sets/{layerSetId}free

One saved configuration by id. Returns 404 when no layer set with that id belongs to this account. `contents` is always "not_exposed".

Parameters

ParameterTypeDescription
layerSetIdstring (required)The layer set id.
Request
curl -H "Authorization: Bearer ond_..." \
  "https://offnadir-delta.com/api/v1/layer-sets/00000000-0000-0000-0000-000000000000"
Response
{
  "layer_set": { "id": "…", "name": "Hormuz watch", "layer_count": 4, "contents": "not_exposed" }
}
GET/api/v1/uploadsfree

The data uploaded to the map on this account, newest first, plus the formats the uploader accepts. Uploading itself is a multipart transfer in the app, so it has no endpoint here — this is how a script or an agent sees what is already there.

Parameters

ParameterTypeDescription
limitinteger 1–100Max uploads to return.
Request
curl -H "Authorization: Bearer ond_..." \
  "https://offnadir-delta.com/api/v1/uploads?limit=2"
Response
{
  "uploads": [
    { "id": "…", "name": "aoi.geojson", "file_type": "geojson", "size_bytes": 21455 }
  ],
  "total": 3,
  "accepted_formats": ["geojson", "geotiff"]
}

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"
}

OpenAPI spec

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

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

For a ChatGPT custom GPT, import the version cut to the GPT Actions limits (how):

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

From headline to satellite evidence

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