Off-Nadir Delta

MCP server & AI agents

The Off-Nadir Delta MCP server lets an AI agent pull live, geolocated world event intelligence — query events and what changed about them, search satellite scenes and plan a collection, read the Daily World Brief, keep a Watchlist, and assess an event or ask an analyst. It serves the tools meant for a conversation; the REST API and the Python SDK carry everything, with every field — for agents that run code and ChatGPT custom GPTs see the end of this page. The server is one stateless Streamable HTTP (JSON-RPC 2.0) endpoint at /api/v1/mcp and works with any MCP-capable client.

Authentication

Two ways to authenticate, in order of preference:

  1. OAuth 2.1 (recommended for interactive clients such as claude.ai remote connectors). The endpoint is an OAuth 2.1 resource server with discovery, dynamic client registration, and PKCE — compatible clients complete the flow automatically; you just log in and approve.
  2. Static API key (for CLIs, scripts, and servers). Pass your ond_… key as a bearer token. Create one in your Developer API settings.

Available on every plan, including Free. What a tool may return follows your plan — a refusal carries denial naming the plan that opens it, and a filter or window your plan does not include is reported in meta instead of being applied. Each metered tool spends from the same monthly token allowance as the app (see each tool's cost below); get_world_brief and get_usage are free. When your balance runs out, calls return a clear error until it resets or you top up.

Client setup

Connecting from the Claude or ChatGPT apps? Those use the one-click OAuth flow (no API key to paste) — start with the two sections below. Wiring it into a CLI, editor, or script? Skip to Claude Code and use a static ond_… key. Everywhere, the endpoint is the same:

text
https://offnadir-delta.com/api/v1/mcp

Claude.ai & Claude Desktop (remote connector)

No API key needed — authentication is handled by OAuth. In claude.ai or Claude Desktop, open Settings → Connectors → Add custom connector, paste the endpoint URL above, then Connect and approve on the Off-Nadir Delta login screen. Claude registers itself automatically (dynamic client registration + PKCE) and completes the flow — you just log in and grant access. How many custom connectors you can add depends on your Claude plan (Anthropic documents the current limits); the connection then spends from your Off-Nadir Delta token balance, and you can revoke it anytime from Developer API settings.

ChatGPT (Developer Mode connector)

Also key-free via OAuth. In ChatGPT, enable Settings → Connectors → Advanced → Developer mode, then Add custom connector, paste the endpoint URL above, and complete the login on the Off-Nadir Delta approval screen. Developer Mode is not available on every ChatGPT plan — check whether yours exposes it. As with Claude, the connection is metered on your token balance and revocable from your Developer API settings.

Claude Code

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

Claude Desktop & generic clients

Add to your mcpServers configuration:

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

Cursor

One-click install, or add the same block to ~/.cursor/mcp.json manually. You complete OAuth (or add your ond_… key) on first use.

VS Code

One-click install, or add to .vscode/mcp.json manually (use the servers key with "type": "http" and the endpoint URL).

Python (programmatic)

python
from offnadir_delta import McpClient

with McpClient(api_key="ond_...") as mcp:
    mcp.initialize()
    print([t["name"] for t in mcp.list_tools()])
    result = mcp.call_tool("query_signals", {"bbox": [22, 44, 40, 53], "recency": "24h"})

First call (free, no tokens)

Verify your key without spending anything: get_usage and get_world_brief cost zero tokens. This JSON-RPC call returns your remaining balance and plan capabilities:

bash
curl -s https://offnadir-delta.com/api/v1/mcp \
  -H "Authorization: Bearer ond_..." \
  -H "Content-Type: application/json" \
  -d '{"jsonrpc":"2.0","id":1,"method":"tools/call","params":{"name":"get_usage","arguments":{}}}'

Then try get_world_brief (also free) for today's AI-synthesized world digest, and call get_usage again before any metered tool to confirm you have enough balance.

Tools

Discover

What is happening, what changed, and what today looks like.

  • query_signals 3 tok

    Geolocated world events from global news media, AI-enriched with severity/GEOINT scores and collection recommendations.

  • get_world_brief free

    AI world brief: daily (prior UTC day, all plans) or weekly/monthly by plan.

  • query_developments 3 tok

    What actually CHANGED about the events in an area, not which articles are new.

  • get_event_thread free

    One event end to end: state plus every change in order — the "new event or update" distinction a feed cannot make.

  • search_entities 1 tok

    Find a named place (airport, base, plant, port, dam, strait…) in any language or by IATA/ICAO code.

  • get_entity 1 tok

    What has happened at one place: the registry record plus its most recent linked events, each with HOW it was linked.

  • get_related_events 1 tok

    The relations recorded for one event: others at the same registry facility, naming the same place, or stored as the same campaign, each saying what is shared; plus reports merged into it.

Plan

Whether a satellite can resolve it, which one, and when it next passes.

  • search_imagery 2 tok

    Search the imagery catalog over an area and window.

  • plan_event_imagery 4 tok

    The deterministic imagery plan for ONE event: checks BOTH sensors exactly once — sentinel-1-grd (SAR, the only look that survives cloud and night) and sentinel-2-l2a — against the event footprint and a pre/post window.

  • rank_imaging_priority 1 tok

    WHERE, and with what class of satellite, observation is most worthwhile now: composite IMPORTANCE crossed with the SPEC CLASS the required resolution demands — coarse, hr (free Sentinel-class) or vhr.

  • predict_satellite_passes 2 tok

    WHEN a place can next be imaged and by WHAT: 13 free-systematic and commercial-taskable families.

Analyze

Turn reporting into a cited assessment you can audit afterwards.

  • assess_signal 5/15 tok

    AI remote-sensing deep-dive for one signal: what to observe, sensors, a collection window.

  • ask_analyst 5–123 tok

    Ask the Delta Analyst an OSINT/GEOINT question; returns a structured brief.

  • get_analyst_job free

    Status and result of an ask_analyst run.

Watch

Stand up continuous coverage and be told only when the answer changes. Creating and listing cost nothing — you are metered only when a check actually fires, or a new acquisition is actually measured.

  • list_watches free

    The Watchlist as one list, each entry with a state bucket.

  • list_notifications free

    What changed across the Watchlist, newest first: one row per change a watch reported, with read state.

  • get_watch free

    One watch end to end: target, state, latest change, measurements with recent series, standing-order questions, and for an event watch its stage, developments, imagery check and thread, plus this account's notes.

  • create_watch free

    Add a target to the Watchlist.

  • update_watch free

    Pause, resume or close a watch.

  • delete_watch free

    Delete a watch and its underlying resources — bound monitored areas with their history, and standing orders.

  • add_note free

    Add a note to a watch, start a thread, or reply.

Account

Pre-flight your token balance before a metered call.

  • get_usage free

    The calling key's remaining token balance and plan capabilities.

Every result carries a one-line natural-language summary you can relay as-is, and successful results include structuredContent (matching each tool's outputSchema) for clients that consume parsed output.

MCP or the API?

A chat client loads every tool into its context and keeps every result in the conversation, and some clients cut a long result. So the MCP server is kept small: it serves the tools you use in a conversation, and its list results are digests on small pages — query_signals up to 50 rows (default 20), search_imagery up to 25 scenes without per-band asset links or the footprint polygon, and query_developments up to 50. Each list result carries full_records: the same query as a REST URL and a Python SDK call, which return every field and larger pages. An agent that can run code (Claude Code, Codex, Cursor) can follow it directly.

These operations are on the REST API and the SDK only. Calling one over MCP returns code: "api_only" with the operation to use:

Former MCP toolRESTPython SDK
survey_observable_eventsGET /api/v1/collection/observabilityclient.collection.observability(...)
create_standing_orderPOST /api/v1/standing-ordersclient.standing_orders.create(...)
list_standing_ordersGET /api/v1/standing-ordersclient.standing_orders.list()
list_layer_setsGET /api/v1/layer-setsclient.workspace.list_layer_sets()
get_layer_setGET /api/v1/layer-sets/{layerSetId}client.workspace.get_layer_set(layer_set_id)
list_uploaded_layersGET /api/v1/uploadsclient.workspace.list_uploads()
list_monitored_areasGET /api/v1/monitoringclient.monitoring.list()
get_monitored_areaGET /api/v1/monitoring/{areaId}client.monitoring.get(area_id)
create_monitored_areaPOST /api/v1/monitoringclient.monitoring.create(...)
delete_noteDELETE /api/v1/watches/{watchId}/notes/{noteId}client.watches.delete_note(watch_id, note_id)
lookup_elevationGET /api/v1/elevationclient.elevation.get(...)
analyze_terrainGET /api/v1/terrainclient.terrain.sar_geometry(...) / client.terrain.profile(...)
measure_index_seriesPOST /api/v1/index-seriesclient.index_series.measure(...)
detect_shipsPOST /api/v1/shipsclient.ships.detect(...)

Try it

Once the server is connected, paste one of these to see the tools chain together. Check your balance first with get_usage (free).

Daily situation briefing (starts free)

prompt
Using the Off-Nadir Delta connector, read the latest World Brief (get_world_brief), then list the most severe world-event signals reported in the last 24 hours (query_signals), each with its place, category and sources. Group them by category and flag anything escalating.

What changed in an area

prompt
With Off-Nadir Delta, show what changed about the events in the Black Sea region (bbox 22, 44, 40, 53) today (query_developments): new events told apart from updates, and why each matters. Open the most important one end to end (get_event_thread).

Signal → satellite plan

prompt
Find a severe security signal in Ukraine from the last 24 hours with Off-Nadir Delta (query_signals), plan the imagery for it (plan_event_imagery), and tell me when a satellite next passes over it (predict_satellite_passes).

Agents that run code: use the SDK

An agent that can run code (Claude Code, Codex, Cursor) gets more from the Python SDK than from MCP: every field, every page, and every operation. Set the key in its environment and give it the instructions below — for example in AGENTS.md or CLAUDE.md at the root of your project.

bash
pip install offnadir-delta
export OFFNADIR_DELTA_API_KEY=ond_...
markdown
## Off-Nadir Delta (geolocated world events, satellite imagery planning)
- Use the Python SDK: `pip install offnadir-delta`. The API key is in the
  OFFNADIR_DELTA_API_KEY environment variable; never print it or write it to a file.
- `from offnadir_delta import Client`; `client = Client()` reads the key.
- Before a metered call, check the balance with `client.usage()` (free).
- Events: `client.signals.list(bbox=[minLon, minLat, maxLon, maxLat], recency="24h")`;
  every page: `client.signals.iterate(...)`. One event in full:
  `client.developments.thread(event_id)`.
- A response states what the plan did not apply or withheld: read
  `meta.filter_clamp`, `meta.window_clamp` and each row's `plan_lock` before
  concluding that something is absent.
- Reference: https://offnadir-delta.com/docs/api (every endpoint),
  https://offnadir-delta.com/docs#python-sdk, runnable programs in the python/ folder of
  https://github.com/Off-Nadir-Lab/offnadir-delta-examples.

ChatGPT custom GPT: GPT Actions

GPT Actions take an OpenAPI document with short descriptions and responses under 100,000 characters. This document carries the conversational operations of the REST API, cut to those limits, with smaller pages (querySignals up to 15 rows, searchImagery up to 8 scenes):

text
https://offnadir-delta.com/api/v1/openapi-gpt-actions.json
  1. In the GPT editor, open Configure and add an action.
  2. Import the schema from the URL above (or paste the JSON it returns).
  3. Set authentication to an API key sent as a Bearer token, and enter your ond_… key.
  4. For the privacy policy, use https://offnadir-delta.com/legal/privacy-policy.
  5. Add instructions such as the ones below.
text
You answer questions about world events and satellite imagery with the
Off-Nadir Delta actions. Call getUsage before a metered action. Keep pages small
(querySignals limit 15 or less). A null value next to plan_lock means
the plan does not show it, not that it is unknown. Cite the event id and the
sources the response gives; do not add facts it does not contain.

The key belongs to the GPT, not to each person using it: every call made through the GPT spends that key's tokens. Keep the GPT to yourself unless you intend that.

Tool argument schemas

Every tool's arguments, types, constraints, and token cost — rendered from the live server definition. Required arguments are marked; everything else is optional.

query_signalsMetered · 3 tok

Geolocated world events from global news media, AI-enriched with severity/GEOINT scores and collection recommendations.

Geolocated world events (geopolitical, security, disaster, infrastructure) distilled from global news media and AI-enriched with severity/GEOINT scores and collection recommendations. Filter by bbox, date window and category.

ParameterTypeReq.ConstraintsDescription
bboxnumber[]no4 items[minLon, minLat, maxLon, maxLat] WGS84. Omit for worldwide.
startDatestringno—Date range start, YYYY-MM-DD (UTC). With endDate. Records start 2026-09-23 (earlier: unrecorded, not quiet). Plan-bounded: meta.window_clamp.
endDatestringno—Date range end, YYYY-MM-DD (UTC), inclusive.
recencystringno15m | 1h | 24hLast reported within. Default 24h when no date range.
categoriesstring[]nokinetic | armed_conflict | maritime | natural_disaster | infrastructure | aviation | humanitarian | protest | diplomacy | other | thermal_anomaly | transit_anomaly | nightlight_anomaly | so2_anomaly | quake_exposure | cyclone_exposureRestrict to these categories. Measured or official-record events (not reporting; never a cause): thermal_anomaly=heat sources; transit_anomaly=ship transits off their recent level; nightlight_anomaly=city night lights under half their usual level; so2_anomaly=SO2 over a volcano; quake_exposure=earthquake near registered facilities; cyclone_exposure=places inside forecast cyclone wind radii.
qstringno—Text search: all words; "phrase"; -exclude.
stagesstring[]noreported | localized | pinpointedreported=country/province, localized=town, pinpointed=block/facility.
minSeverityBandintegerno4 | 6 | 9severity_band >= this.
minPublishersintegerno2 | 3 | 5At least this many publishers.
marketsstring[]nooil | natural_gas | grain | shipping | defense | metals | semiconductors | fx | equitiesExposed markets (physical/supply channel).
placementstringnoall | map | listmap=a point; list=country/province only.
linkedTostringno—Shares a connector: facility:<entity_id>, place:<place_key>, actor:<name>. Plan-gated.
watchIdstringno—Events linked to this watch (list_watches id).
sortstringnolatest | oldest | geointDefault latest. geoint (plan-gated): observable, then geoint_score.
updatedSincestringno—Refolded at/after (ISO).
limitintegerno1…50Page size (default 20). Rows are digests; full rows: full_records.
cursorstringno—From meta.next_cursor.

Result (structuredContent)

FieldTypeAlwaysDescription
metaobjectyesQuery echo, token charge/balance (meta.tokens), and pagination where applicable.
summarystringnoOne-line natural-language summary of the result, ready to relay to a user.
signalsobject[]yes
full_recordsobjectnoThe same query on the REST API and the Python SDK, which return every field and larger pages.
get_world_briefFree

AI world brief: daily (prior UTC day, all plans) or weekly/monthly by plan. freshness.is_stale: no newer day, so relay as stale.

The World Brief — an AI digest of worldwide event signals (headline, executive summary, top developments, per-theme roll-up, ranked signals). period=daily (default) covers the previous UTC day and is open on every plan; weekly and monthly are opened by the key owner's plan. If freshness.is_stale, a newer day is not yet available, so relay it as possibly out of date.

ParameterTypeReq.ConstraintsDescription
periodstringnodaily | weekly | monthly
datestringno—YYYY-MM-DD UTC (daily: within the plan window); weekly/monthly: last day. Default latest

Result (structuredContent)

FieldTypeAlwaysDescription
briefobjectyes
freshnessobjectnoFreshness of the returned brief: brief_date, generated_at, age_hours, freshness (operational|delayed|degraded), is_stale, note.
get_usageFree

The calling key's remaining token balance and plan capabilities. Pre-flight a metered call with it.

The calling key's remaining token balance and plan capabilities. Pre-flight a metered call with it.

No arguments.

Result (structuredContent)

FieldTypeAlwaysDescription
tokensobjectyes
planobjectyes
search_imageryMetered · 2 tok

Search the imagery catalog over an area and window. Metadata only, no pixels. eventDate tags each scene pre/post — **a same-day scene is same_day_unknown, never post** — and eventPoint/eventAoi add target_relation, so a scene that only clips the bbox is not read as covering the event.

Search the imagery catalog (Sentinel-1, Sentinel-2, NISAR L-band) over an area and window. Metadata only, no pixels. With eventDate each scene is tagged timing=pre/post/same_day_unknown — **a same-day scene is same_day_unknown, never post**, without a real event time — and meta reports bracketing and sar_pair_status. eventPoint/eventAoi add target_relation, so a scene that only clips the wide bbox is not read as covering the event.

ParameterTypeReq.ConstraintsDescription
bboxnumber[]yes4 items[minLon, minLat, maxLon, maxLat] WGS84. Required.
collectionstringnosentinel-1-grd | sentinel-1-rtc | sentinel-2-l2a | NISAR_L2_GCOV_PROVISIONAL_V1Catalog collection. Default sentinel-2-l2a.
startDatestringno—Date range start, YYYY-MM-DD (UTC). With endDate; omit both for the last 7 days (max 30).
endDatestringno—Date range end, YYYY-MM-DD (UTC), inclusive.
eventDatestringno—Widens to the canonical pre/post span with bracketing; sentinel-1-grd adds sar_pair_status.
eventPointnumber[]no2 items[lon, lat] WGS84. Drives covers_event_point / usable_for_event.
eventAoinumber[]no4 items[minLon,minLat,maxLon,maxLat]. Drives intersects_event_aoi / coverage_ratio.
eventTimestampstringno—ISO 8601 event time — promotes same-day scenes to pre/post.
cloudCoverMaxnumberno0…100Sentinel-2 only: max cloud cover %.
limitintegerno1…25Max scenes. Default 10.

Result (structuredContent)

FieldTypeAlwaysDescription
metaobjectyesQuery echo, token charge/balance (meta.tokens), and pagination where applicable.
summarystringnoOne-line natural-language summary of the result, ready to relay to a user.
scenesobject[]yes
full_recordsobjectnoThe same query on the REST API and the Python SDK, which return every field and larger pages.
plan_event_imageryMetered · 4 tok

The deterministic imagery plan for ONE event: checks BOTH sensors exactly once — sentinel-1-grd (SAR, the only look that survives cloud and night) and sentinel-2-l2a — against the event footprint and a pre/post window. **A VHR recommendation never invalidates what the free catalog showed.**

The deterministic imagery plan for ONE event: the server checks BOTH sensors exactly once — sentinel-1-grd (SAR, the only look that survives cloud and night) and sentinel-2-l2a — against the event footprint and a pre/post window, reporting how many scenes COVER the event and are usable, SAR pair status and bracketing. **A VHR recommendation never invalidates what the free catalog already showed.** An event with no resolvable footprint is refused, not guessed.

ParameterTypeReq.ConstraintsDescription
event_idstringyes—The `id` (UUID) from query_signals; the server resolves its point and AOI.
analysis_goalstringyesdamage_assessment | flood_mapping | wildfire_assessmentWhat the imagery must establish — decides the lead collection and cloud gating.
event_datestringno—YYYY-MM-DD. The event row supplies it when known.

Result (structuredContent)

FieldTypeAlwaysDescription
metaobjectyesQuery echo, token charge/balance (meta.tokens), and pagination where applicable.
summarystringnoOne-line natural-language summary of the result, ready to relay to a user.
planobjectyes
rank_imaging_priorityMetered · 1 tok

WHERE, and with what class of satellite, observation is most worthwhile now: composite IMPORTANCE crossed with the SPEC CLASS the required resolution demands — coarse, hr (free Sentinel-class) or vhr. Deterministic.

WHERE, and with what class of satellite, observation is most worthwhile now. Crosses each event's composite IMPORTANCE with the SPEC CLASS its resolution demands: coarse (<=100 m), hr (<=10 m, free Sentinel-class) or vhr (sub-metre, commercial). Deterministic, no LLM.

ParameterTypeReq.ConstraintsDescription
bboxnumber[]no4 items[west, south, east, north] WGS84. Omit for global.
start_datestringno—YYYY-MM-DD, inclusive. Default today. Records start 2026-09-23 (earlier: unrecorded, not quiet). Plan-bounded: meta.window_clamp.
end_datestringno—YYYY-MM-DD, inclusive. Default today; capped at 30 days.
categoriesstring[]no—Restrict to these Delta categories.
min_geoint_scorenumberno—Drop events below this GEOINT score before ranking.
top_nnumberno1…50How many top targets to return (default 12).

Result (structuredContent)

FieldTypeAlwaysDescription
metaobjectyesQuery echo, token charge/balance (meta.tokens), and pagination where applicable.
summarystringnoOne-line natural-language summary of the result, ready to relay to a user.
priorityobjectyes
predict_satellite_passesMetered · 2 tok

WHEN a place can next be imaged and by WHAT: 13 free-systematic and commercial-taskable families. **Every pass is a GEOMETRIC access opportunity** — no operator plan is consulted. retrieval_ok:false means timing is UNAVAILABLE, never "no passes".

WHEN a place can next be imaged and by WHAT. SGP4 over day-cached elements for 13 families: free SYSTEMATIC (Sentinel-1/2, Landsat, NISAR) and commercial AGILE (WorldView, ICEYE, Capella, SkySat, Umbra, Synspective, iQPS, RADARSAT-2, COSMO-SkyMed — taskable access needing a paid order, NOT a guaranteed collect). **Every pass is a GEOMETRIC access opportunity** — no operator plan is consulted. retrieval_ok:false means timing is UNAVAILABLE, never "no passes".

ParameterTypeReq.ConstraintsDescription
latnumberno-90…90Target latitude. Required unless bbox is given.
lonnumberno-180…180Target longitude. Required unless bbox is given.
bboxnumber[]no4 items[west, south, east, north] WGS84. Its CENTRE is the target if lat/lon are omitted.
start_datestringno—YYYY-MM-DD (UTC), inclusive. Default today.
end_datestringno—YYYY-MM-DD (UTC), inclusive. Default start+2 days; 7-day horizon.
satellitesstring[]nosentinel-1 | sentinel-2 | landsat | worldview | iceye | capella | skysat | umbra | synspective | iqps | radarsat-2 | cosmo-skymed | nisarFamilies to consider. Omit for all thirteen.
max_passesnumberno1…100Maximum passes to return, soonest first (default 40).

Result (structuredContent)

FieldTypeAlwaysDescription
metaobjectyesQuery echo, token charge/balance (meta.tokens), and pagination where applicable.
summarystringnoOne-line natural-language summary of the result, ready to relay to a user.
passesobject[]yes
freshnessobjectno
assess_signalMetered · 5/15 tok

AI remote-sensing deep-dive for one signal: what to observe, sensors, a collection window. Also returns a deterministic `context` whose `imagery_handoff.parameters` are the exact search_imagery inputs. Cached per signal; not-observable signals are rejected before any charge.

AI remote-sensing deep-dive for one signal: what to observe, recommended sensors, a collection window. Also returns a deterministic `context` whose `imagery_handoff.parameters` are the exact search_imagery inputs for real pre/post candidates. Cached per signal, not re-charged. Signals that are not satellite-observable are rejected BEFORE any charge.

ParameterTypeReq.ConstraintsDescription
eventIdstringyes—Signal id (UUID) from query_signals.
kindstringnoquick | deepAssessment depth. Defaults to quick.

Result (structuredContent)

FieldTypeAlwaysDescription
kindstringnoAssessment depth actually run ("quick" | "deep").
cachedbooleannoTrue when a prior assessment for this signal was reused (no re-charge).
modelstringno
contentobjectno
metaobjectnoQuery echo, token charge/balance (meta.tokens), and pagination where applicable.
contextobjectnoDeterministic collection context for this signal — not model prose. Use `imagery_handoff` to go straight from the assessment to real scene candidates.
statusstringnoPresent ONLY on a pre-charge rejection; `content` and `meta` are then absent.
event_idstringnoEchoed on a rejection so the caller can tell which signal it was.
chargedintegernoTokens charged on a rejection — always 0.
reasonstringnoWhy the signal was rejected before any charge.
reason_codesstring[]no
ask_analystMetered · 5–123 tok

Ask the Delta Analyst an OSINT/GEOINT question; returns a structured brief. **Durable async**: {status:"processing", job_id} comes back immediately and the run finishes in a background worker. Fetch with get_analyst_job, or re-send the SAME idempotencyKey (no second charge).

Ask the Delta Analyst an OSINT/GEOINT question. Agentic multi-step analysis returning a structured brief (summary, findings with collection recommendations, assessment, citations). **Durable async**: returns {status:"processing", job_id} immediately and finishes in a background worker, so it survives a client timeout. Fetch with get_analyst_job, or re-send the SAME idempotencyKey (no second charge).

ParameterTypeReq.ConstraintsDescription
questionstringyes—The analytic question (≤ 500 chars).
bboxnumber[]no4 itemsFocus bbox [minLon, minLat, maxLon, maxLat] WGS84.
modestringnofast | deepfast (default) or deep. Deep widens reasoning and gathering: slower, ceiling 123→415, still charged by usage.
idempotencyKeystringno—At-most-once key. Re-sending the SAME key returns the SAME run with no second charge, so a timeout is recoverable.

Result (structuredContent)

FieldTypeAlwaysDescription
briefobjectno
metaobjectnoQuery echo, token charge/balance (meta.tokens), and pagination where applicable.
statusstringno"processing" when the run is still going (poll get_analyst_job or re-send the same idempotencyKey).
job_idstringnoId of the analyst run — pass to get_analyst_job (also at GET /api/v1/analyst/{job_id}).
progressobjectnoPipeline progress while the job is processing. completed_steps reaches total_steps ONLY when status is "done".
estimated_chargeobjectnoThe charge ceiling quoted for THIS run, fixed at enqueue (統合改善指示書 P1-1). The completed run reports the actual charge in meta.tokens.charged and echoes this ceiling as meta.tokens.maximum_promised; actual never exceeds it.
messagestringno
get_analyst_jobFree

Status and result of an ask_analyst run. Status is "processing", "done" or "error"; result_quality.status is **SEPARATE** — a done job can carry a partial result.

Status and result of an ask_analyst run by job_id. Status is ONLY "processing", "done" or "error". result_quality.status ("complete" | "partial") is **SEPARATE from job status**: a done job can carry a partial result. Visible only to the key that created it.

ParameterTypeReq.ConstraintsDescription
job_idstringyes—The job_id returned by ask_analyst.

Result (structuredContent)

FieldTypeAlwaysDescription
job_idstringno
statusstringno"processing" | "done" | "error".
progressobjectnoPipeline progress while the job is processing. completed_steps reaches total_steps ONLY when status is "done".
briefobjectno
metaobjectnoQuery echo, token charge/balance (meta.tokens), and pagination where applicable.
errorstringnoFailure reason when status is "error".
messagestringno
created_atstringno
updated_atstringno
query_developmentsMetered · 3 tok

What actually CHANGED about the events in an area, not which articles are new. Each change is labelled `world` (the event's own state moved) or `measurement` (what we can see moved). Plan-dependent values say so (`locked_by`).

What actually CHANGED about the events in an area, not which articles are new. Each change carries what it was and what it became, labelled `world` (the event's own state moved) or `measurement` (what WE can see moved: location resolved, imagery arrived, SAR pair ready). The right source for "what is new since last time".

ParameterTypeReq.ConstraintsDescription
bboxnumber[]no4 items[minLon, minLat, maxLon, maxLat]. Omit for worldwide.
start_datestringno—Date range start (YYYY-MM-DD). With end_date; omit both for today. Records start 2026-09-23 (earlier: unrecorded, not quiet). Plan-bounded: meta.window_clamp.
end_datestringno—Date range end (YYYY-MM-DD), inclusive.
categoriesstring[]no—Restrict to these signal categories.
axesstring[]noattributed_actor | casualties_disputed | casualties_injured | casualties_killed | distinct_hosts | escalation_trend | geoint_score | location_level | means_reported | member_count | occurred_at | point_plottable | quality_status | severity_score | stage | target_effect | target_status | targets_namedRestrict to these axes. An unknown value errors rather than returning nothing.
notable_onlybooleanno—Default true. False returns every recorded change.
include_member_countbooleanno—Default false. "One more report arrived" is not news about the event.
limitnumberno1…50Developments to return (default 20).
offsetnumberno0…∞Skip this many; meta.total_count is the window.

Result (structuredContent)

FieldTypeAlwaysDescription
summarystringnoOne-line natural-language summary of the result, ready to relay to a user.
metaobjectnoQuery echo, token charge/balance (meta.tokens), and pagination where applicable.
developmentsobject[]yes
get_event_threadFree

One event end to end: state plus every change in order — the "new event or update" distinction a feed cannot make. `occurred_at_basis` distinguishes stated, absent and never measured. Plan-dependent: see `locked_by`, `plan_lock`.

One event end to end: current state and every change in order — the "new event or update to an old one" distinction a feed cannot make. Returns the canonical event (where, when it HAPPENED as distinct from when it was reported, casualties, attribution and its grounding), a timeline and every source. `occurred_at_basis` distinguishes stated, absent and never measured.

ParameterTypeReq.ConstraintsDescription
event_idstringyes—Event id (UUID). A folded id resolves to its event; merged_from_request says so.

Result (structuredContent)

FieldTypeAlwaysDescription
summarystringnoOne-line natural-language summary of the result, ready to relay to a user.
canonical_eventobjectyes
timelineobject[]yes
sourcesobject[]no
list_watchesFree

The Watchlist as one list, each entry with a state bucket. `updated_since` returns only watches whose CONTENT changed after that instant — what a synchronised copy should poll.

The Watchlist: measured areas, event-watched areas and tracked events as one list, each with a state bucket (needs_attention, changed_today, awaiting_data, stable). A watch groups everything on the same target. `updated_since` returns only watches whose CONTENT changed after that instant — what a synchronised copy should poll.

ParameterTypeReq.ConstraintsDescription
updated_sincestringno—ISO 8601. Only watches whose CONTENT changed after it — the evidence, not renames.
cursorstringno—Cursor from meta.next_cursor.
limitintegerno1…500Watches per page when paging (1..500, default 100).

Result (structuredContent)

FieldTypeAlwaysDescription
summarystringnoOne-line natural-language summary of the result, ready to relay to a user.
watchesobject[]yes
bucketsobjectno
limitsobjectno
metaobjectno
list_notificationsFree

What changed across the Watchlist, newest first: one row per change a watch reported, with read state. Reading does not mark them read. A value the plan does not open is `locked`, never filled in.

The Notification Center: every change a watch reported — an event development, a newly related event, an anomaly in a measurement, a new event at a site, a new scene, a fired standing order — one row each, newest first, the same rows as the bell in the app and the Watch update emails. `unread_count` in meta is the total unread regardless of filters. **Listing does not mark anything read**: the unread state belongs to the user's inbox, and a conversation reading it should not clear the bell. Marking read is POST /api/v1/notifications/read or client.notifications.mark_read in the Python SDK — deliberately not an MCP tool (inbox housekeeping, not something a conversation needs). A value the plan does not open is withheld: `locked` is true and `lock_plan` names the plan that opens it. For one watch in full, follow watch.id into get_watch.

ParameterTypeReq.ConstraintsDescription
unread_onlybooleanno—Only notifications not yet read.
kindstringnoevent_development | related_event | monitoring_anomaly | site_event | new_imagery | standing_order
watch_idstringno—Id from list_watches.
cursorstringno—Cursor from meta.next_cursor.
limitintegerno1…25Rows per page (1..25, default 10).

Result (structuredContent)

FieldTypeAlwaysDescription
summarystringnoOne-line natural-language summary of the result, ready to relay to a user.
notificationsobject[]yes
metaobjectno
full_recordsobjectnoThe same query on the REST API and the Python SDK, which return every field and larger pages.
get_watchFree

One watch end to end: target, state, latest change, measurements with recent series, standing-order questions, and for an event watch its stage, developments, imagery check and thread, plus this account's notes.

One watch end to end: target, state, latest meaningful change, measurements with recent series, standing-order questions, and for an event watch its stage (how precisely it is located), developments, imagery availability and full thread — plus this account's notes and judgments. An event watch reports signal.imagery_check: whether imagery is being checked for it and, if not, why (see create_watch for the rule and the charge). The FULL measurement history is on the REST API (GET /api/v1/monitoring/{areaId}) and the Python SDK (client.monitoring.get).

ParameterTypeReq.ConstraintsDescription
watch_idstringyes—Id from list_watches.
include_passesbooleanno—Add the collection outlook. Costs the same as predict_satellite_passes; omit it and the call is free.

Result (structuredContent)

FieldTypeAlwaysDescription
summarystringnoOne-line natural-language summary of the result, ready to relay to a user.
watchobjectyes
threadobjectno
create_watchFree

Add a target to the Watchlist. EVENT: the signal's event id binds the canonical event — **never watch an article URL**. AREA: a bbox. SITE: entity_id or lat+lon, and scale. Free to create; where the plan includes the collection section, a watched event's imagery is then checked at 1 token a day (up to 14 days).

Add a target to the Watchlist. EVENT: pass the signal's event id and the server binds the canonical event, so later reporting and even a cluster merge stay on the same watch — **never watch an article URL**. AREA: pass a bbox of at most 100,000 km² (larger is refused with 400 invalid_input; for a whole country use SITE at scale "country"); measuring there and a standing order on its events are created over the REST API or the Python SDK (monitoring / standing-orders) and auto-join the same watch. SITE: watch a specific place for change — a registry facility (entity_id from search_entities) or any point (lat, lon) — at a scale: "facility" (radius 250 m–3 km; measured from imagery by default when the kind of facility has a suggested measurement, e.g. vessel counts for a port), "city" (5–20 km; measured only when you pass a metric) or "country" (reporting only; never measured). Linked events come back with how they were linked (named / nearby / country). A refused explicit metric creates nothing; a refused default is reported in measurement.reason. Measuring uses a monitored-area slot and costs the same as a monitored area. IMAGERY FOR A WATCHED EVENT: creating the watch is free. Where the account's plan includes the collection section (the imaging recommendation), imagery of a watched event is then checked automatically at 1 token a day per watch, until before and after scenes are found or 14 days after the first report; a day without enough tokens is skipped, not charged. On other plans imagery is not checked and nothing is charged. Imagery is checked only for watched events — an event that is not watched carries no imagery status.

ParameterTypeReq.ConstraintsDescription
target_typestringyesevent | area | site
event_idstringno—EVENT: signal uuid.
bboxnumber[]no4 itemsAREA: [W,S,E,N] WGS84, at most 100,000 km² (larger → 400).
entity_idstringno—SITE: registry id
latnumberno—
lonnumberno—
scalestringnofacility | city | country
radius_mnumberno—
metricstringno—SITE: metric or "none".
namestringno—Label.
notify_emailbooleanno—Email on changes.
imagery_alertsbooleanno—AREA/SITE: new-scene alerts.

Result (structuredContent)

FieldTypeAlwaysDescription
summarystringnoOne-line natural-language summary of the result, ready to relay to a user.
watchobjectyes
already_existedbooleanno
update_watchFree

Pause, resume or close a watch. **Pausing is not a display state**: monitored areas stop being measured and charged, standing orders stop checking. **Closing states how the question ended** (close_reason).

Pause and resume a watch, CLOSE it when the question is settled, or turn on `imagery_alerts`. **Closing states how the question ended** and requires `close_reason`: `resolved` (an answer was reached), `lapsed` (interest moved on without an answer) or `false_alarm` (never a watchable event). A closed watch stops notifying, stops its bound measurements and standing orders, and frees an active slot — so close rather than delete when the record is worth keeping. **Pausing is not a display state over live billing**: monitored areas stop being measured and charged, standing orders stop checking. Resuming restarts them, standing orders due immediately. Use it instead of delete_watch when the history is worth keeping. `imagery_alerts` is for AREA watches and facility- or city-scale SITE watches: it reports that a NEW scene now covers the area — availability, never what is in the scene. For an event, post-event coverage already arrives as a development on the event itself. `notify_chat` posts the watch’s notifications to the Slack or Discord channel the user connected in account settings (connecting happens in the app); with none connected the setting is kept and nothing is posted.

ParameterTypeReq.ConstraintsDescription
watch_idstringyes—Id from list_watches.
statusstringnoactive | paused | saved | closed"paused" stops the bound checks; "active" resumes them; "closed" ends the question and needs close_reason.
close_reasonstringnoresolved | lapsed | false_alarmRequired when status is "closed". resolved = an answer was reached; lapsed = interest moved on without an answer; false_alarm = never a watchable event. They point at different things to fix, so do not collapse them.
notify_emailbooleanno—Email on changes.
notify_chatbooleanno—Also post changes to the Slack/Discord channel connected in settings.
imagery_alertsbooleanno—AREA/SITE: new-scene alerts.

Result (structuredContent)

FieldTypeAlwaysDescription
summarystringnoOne-line natural-language summary of the result, ready to relay to a user.
watchobjectno
delete_watchFree

Delete a watch and its underlying resources — bound monitored areas with their history, and standing orders. To stop without losing them, pause with update_watch.

Delete a watch and its underlying resources — bound monitored areas with their measurement history, and standing orders — exactly as the in-app Watchlist does. To stop without losing anything, pause with update_watch instead.

ParameterTypeReq.ConstraintsDescription
watch_idstringyes—Id from list_watches.

Result (structuredContent)

FieldTypeAlwaysDescription
summarystringnoOne-line natural-language summary of the result, ready to relay to a user.
deletedstringno
removedobjectno
add_noteFree

Add a note to a watch, start a thread, or reply. **Confidence is what separates a note from a judgment**: with one it is a JUDGMENT that supersedes the previous judgment rather than overwriting it.

Add a note to a watch, start a thread, or reply — one append-only ledger. A title makes it a thread; replies are one level deep. **Confidence is what separates a note from a judgment**: with one it is a JUDGMENT that supersedes the previous judgment rather than overwriting it. It does not change the event's public record.

ParameterTypeReq.ConstraintsDescription
watch_idstringyes—Id from list_watches.
notestringyes—What you want kept with this watch.
titlestringno—A heading makes it a thread. A reply cannot have one.
parent_idstringno—Reply to this note id. One level deep.
confidencestringnohigh | moderate | lowHow sure the judgment is — about the evidence, not the event. Omit and it stays a note.
likelihoodstringnovery unlikely | unlikely | roughly even chance | likely | very likely | almost certainHow probable the thing itself is (ICD 203). Omit rather than guess.
gapsstring[]no—What would change this judgment. Only meaningful with a confidence.
next_checkstringno—When/what to look at next.

Result (structuredContent)

FieldTypeAlwaysDescription
summarystringnoOne-line natural-language summary of the result, ready to relay to a user.
noteobjectyes
search_entitiesMetered · 1 tok

Find a named place (airport, base, plant, port, dam, strait…) in any language or by IATA/ICAO code. Max 50.

Find a place in the location registry — airports, military and naval bases, launch sites, nuclear facilities, ports, power plants, dams, bridges, government sites, chokepoints and named seas. Matches names in many languages and IATA/ICAO codes, most prominent first. At most 50 rows, no pagination. Cite the returned attribution.

ParameterTypeReq.ConstraintsDescription
querystringyes—Name or partial name (at least 2 characters).
subtypesstring[]noport_facility | military_base | airport | refinery_energy | nuclear_facility | launch_site | dam | bridge | government_site | chokepoint | water_bodyKinds to keep. Omit for all.
limitnumberno—Max rows (1-50, default 20).

Result (structuredContent)

FieldTypeAlwaysDescription
summarystringnoOne-line natural-language summary of the result, ready to relay to a user.
entitiesobject[]yes
limitnumberno
get_entityMetered · 1 tok

What has happened at one place: the registry record plus its most recent linked events, each with HOW it was linked. **`link_kind: proximity` is an association, not a statement that the event happened there.**

What has happened at one place: the registry record plus its most recent linked events (`event_count` is the total), each carrying HOW it was linked — `link_kind` `named_location` (a report named this place as where it happened), `named_target` (named it as the target), `measured` (a satellite measurement recorded near it, with `distance_m` — what was measured, not a report of what happened) or `proximity` (located within range, with `distance_m`). **Read the basis: proximity is an association, not a statement that the event happened there.**

ParameterTypeReq.ConstraintsDescription
entity_idstringyes—The id from search_entities.

Result (structuredContent)

FieldTypeAlwaysDescription
summarystringnoOne-line natural-language summary of the result, ready to relay to a user.
entityobjectyes
eventsobject[]no
event_countnumberno
events_24hnumberno
get_related_eventsMetered · 1 tok

The relations recorded for one event: others at the same registry facility, naming the same place, or stored as the same campaign, each saying what is shared; plus reports merged into it. Merely-nearby events are not relations and are not returned. Plan-dependent: see `plan_lock`.

The relations recorded for one event — nothing is inferred on request. **Every entry in `related` says what the two events share** in `shares`: the same registry facility, the same named place (resolved to a town or finer), or a relation stored when the events were processed (`same_campaign`). Reports of the same event merged into it are in `merged_in`. Events that are merely close in space and time are not relations and are not returned. Each facility and place lists its most recent other events; `other_count` is the total.

ParameterTypeReq.ConstraintsDescription
event_idstringyes—The event id (a UUID) from query_signals.

Result (structuredContent)

FieldTypeAlwaysDescription
summarystringnoOne-line natural-language summary of the result, ready to relay to a user.
anchorobjectno
facilitiesobject[]no
placesobject[]no
relatedobject[]yes
merged_inobject[]no
accountingobjectno

REST ↔ MCP parameters

The MCP tools mirror the REST API — same filters, same token costs. Some argument names differ: REST uses snake_case query params while several MCP arguments are camelCase (the table lists them), and a bounding box is a comma-string in REST but a 4-number array in MCP.

REST (query param)MCP (argument)
cloud_cover_maxcloudCoverMax
end_dateendDate
entityIdentity_id
event_aoieventAoi
event_dateeventDate
event_pointeventPoint
event_timestampeventTimestamp
eventIdevent_id
Idempotency-KeyidempotencyKey
include_member_countincludeMemberCount
jobIdjob_id
linked_tolinkedTo
min_publishersminPublishers
min_severity_bandminSeverityBand
qquery
start_datestartDate
updated_sinceupdatedSince
watch_idwatchId
watchIdwatch_id
bbox (comma string)bbox (number[4])

Example response

query_signals over MCP (digest rows; values are illustrative). Full field definitions are in the signals://schema resource.

json
{
  "meta": {
    "start_date": "2026-09-28",
    "end_date": "2026-09-29",
    "count": 20,
    "total_count": 1181,
    "next_cursor": "My5tWWpiLXU1YWg4UWVRTU5E",
    "has_more": true,
    "tokens": {
      "charged": 3,
      "remaining": 6836
    }
  },
  "summary": "20 signal(s) 2026-09-28→2026-09-29 (more pages available). Top categories: kinetic (8), armed_conflict (5).",
  "signals": [
    {
      "id": "67f0dcc5-19e4-4a38-8c7c-fc3fa1ad4513",
      "title": "Strike reported near a port",
      "category": "kinetic",
      "summary": "An airstrike hit an apartment building in the port district; officials report casualties…",
      "last_reported_at": "2026-09-29T07:45:00+00:00",
      "country": "Example Country",
      "location": "port district",
      "lat": 31.44,
      "lng": 34.36,
      "stage": "reported",
      "severity_band": 7,
      "geoint_score": 9,
      "escalation_trend": "escalating",
      "distinct_hosts": 1,
      "collection": {
        "sensor": "vhr-optical",
        "level": "GSD<1m",
        "observable": false
      },
      "plan_lock": null
    }
  ],
  "full_records": {
    "note": "Rows here are digests. get_event_thread gives one event in full; the REST API and the Python SDK return every field and up to 500 rows per page.",
    "rest": "GET https://offnadir-delta.com/api/v1/signals?recency=24h",
    "sdk": "client.signals.list(recency=\"24h\")",
    "docs": "https://offnadir-delta.com/docs/api#ep-signals"
  }
}

Resources

brief://latestThe most recent AI-synthesized Daily World Brief (JSON). Free.
signals://schemaJSON Schema of the public Signal shape returned by query_signals / /api/v1/signals.
usage://currentRemaining token balance and plan capabilities for the calling key. Free.
status://currentHow current the data is (ingestion/enrichment frontier), the Daily World Brief status, and an Operational/Delayed/Degraded roll-up. Free.
brief://{date}The Daily World Brief for a specific UTC date (YYYY-MM-DD). Free.
watch://{watch_id}A Watchlist entry with its current state, latest meaningful change, measurements, (for event watches) the full event thread, and the notes kept against it. The same body get_watch returns. Free.

Looking for the underlying REST endpoints, parameters, and token costs? See the API reference.

Frequently Asked Questions

How is MCP different from the REST API and the SDK?▼
MCP is for an assistant in a conversation: it serves the tools meant for a chat and returns digests that fit one. The REST API and the Python SDK are for code: every operation and every field, pages of up to 500 rows, and the operations MCP does not serve (monitored areas, standing orders, saved layer sets, elevation, terrain, index series, ship detection). Both use the same key and token balance, and each MCP list result carries the equivalent REST call.
Is it authenticated and metered?▼
Yes. Chat apps connect over OAuth 2.1; CLIs, editors and scripts use an ond_ API key as a bearer token. Every plan includes it, including Free. Metered tools spend your token balance (get_world_brief and get_usage are free); what a tool may do follows your plan, and a refusal names the plan that opens it. Keys and connections are revocable from Account → Developer API.
Why does my client still list tools that are not on this page?▼
MCP clients keep the tool list they fetched when they connected. Reconnect the server to refresh it. A tool that moved to the REST API and the SDK answers with code api_only and the operation to use instead.

From headline to satellite evidence

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