A collection manager’s loop — rank targets, plan imagery, check the next pass
You can only task or review so much imagery; the question is which events deserve it, what to collect, and when the next opportunity is.
Steps
- Rank where observation is most worthwhile (collection priority).
- For the top event, get the deterministic SAR + optical plan — the all-weather look can never be silently skipped.
- Check when the place can next be imaged, and by which satellite.
In the web app
See imaging priority on the Watchfloor →
REST
Authenticate with Authorization: Bearer ond_… (create a key at /account/api). Try any of these live in the Playground.
Rank imaging priority
curl "https://offnadir-delta.com/api/v1/collection/priority?top_n=10" \
-H "Authorization: Bearer ond_YOUR_API_KEY"Plan imagery for one event
curl "https://offnadir-delta.com/api/v1/collection/plan?event_id=1315064079&analysis_goal=damage_assessment" \
-H "Authorization: Bearer ond_YOUR_API_KEY"Next passes over the target
curl "https://offnadir-delta.com/api/v1/passes?lat=48.85&lon=2.35" \
-H "Authorization: Bearer ond_YOUR_API_KEY"Python
This is the runnable example shipped with the SDK (pip install offnadir-delta, then set OFFNADIR_DELTA_API_KEY).
"""A collection manager's loop: rank targets, plan the imagery, check the next pass.
This is the workflow the SDK could not express before 0.4.0 — you could read events but
not act on them. Nothing here invents a capability: every step reports what the data can
and cannot support, which is the part a tasking decision actually rests on.
Run with: OFFNADIR_DELTA_API_KEY=ond_... python examples/collection_tasking.py
"""
from offnadir_delta import Client
# bbox = [min_lon, min_lat, max_lon, max_lat].
AOI = [-180.0, -85.0, 180.0, 85.0] # worldwide; narrow this to your own area of interest.
def main() -> None:
with Client() as client:
# 1. WHAT IS WORTH IMAGING. `total_available` vs `returned` matters: a short list
# can mean "few candidates" or "few that survived the readiness gates", and
# those are different situations for a collection manager.
ranked = client.collection.priority(bbox=AOI, top_n=5)
body = ranked.priority
if body is None:
print("No priority body returned.")
return
print(f"{body.returned} target(s) returned of {body.total_available} available")
# An empty list is a real answer, and the exclusion breakdown is the useful part
# of it: it says WHICH gate emptied the funnel (measured worldwide on 2026-07-28,
# 28 of 34 candidates were dropped for `geo_not_ready` alone). A collection
# manager needs that, not a silent zero.
if not ranked.targets:
print("\nNothing is collection-ready. Excluded by:")
for gate, count in sorted((body.excluded or {}).items(), key=lambda kv: -kv[1]):
if count:
print(f" {gate:32s} {count}")
print("\nResolve the blocking gate (usually an event-specific coordinate) and re-run.")
return
print()
for target in ranked.targets:
blockers = target.readiness_blockers or []
state = "ready" if target.collection_ready else f"blocked ({', '.join(blockers) or 'n/a'})"
print(f" {target.global_event_id} {(target.headline or '')[:52]:52s} {target.rs_level or '-':10s} {state}")
# 2. PLAN THE FIRST READY TARGET. The plan searches each collection exactly once
# against the event footprint, so the all-weather SAR look is never skipped in
# favour of whichever optical scene happened to be cloud-free.
target = next((t for t in ranked.targets if t.collection_ready), ranked.targets[0])
print(f"\nPlanning imagery for {target.global_event_id}…")
plan = client.collection.plan(event_id=target.global_event_id, analysis_goal="damage_assessment")
for step in (plan.plan.steps if plan.plan else []):
pair = f" sar_pair={step.sar_pair_status}" if step.sar_pair_status else ""
print(f" {step.collection:18s} returned={step.returned_count} usable={step.usable_count}{pair}")
# 3. WHEN CAN IT NEXT BE SEEN. `collection_mode` is the field to read: a
# systematic satellite will acquire on its own plan (the data will exist), an
# agile one only images if somebody orders it. A pass is an OPPORTUNITY.
aoi = target.rs_aoi or []
if len(aoi) != 4:
print("\n(no AOI on the target — pass prediction needs a point or bbox)")
else:
lon, lat = (aoi[0] + aoi[2]) / 2, (aoi[1] + aoi[3]) / 2
passes = client.collection.passes(lat=lat, lon=lon, max_passes=5)
if passes.retrieval_ok is False:
print("\n! orbital elements could not be refreshed — treat these windows as indicative")
print()
for p in passes.passes:
print(f" {p.start} {p.satellite:22s} {p.collection_mode or '-':10s} peak={p.peak_elevation_deg}°")
# 4. WATCH IT. Creating the order is free; only a check that finds something new
# runs the Analyst and is metered, and the response states the monthly ceiling.
order = client.standing_orders.create(bbox=AOI, name="aoi-watch-example", cadence="weekly")
print(f"\n{order.summary}")
if order.order and order.order.id:
client.standing_orders.delete(order.order.id) # example only — don't leave it behind
print("(example order deleted)")
if __name__ == "__main__":
main()
MCP
Connect once, from any MCP client:
claude mcp add --transport http off-nadir-delta \
https://offnadir-delta.com/api/v1/mcp \
--header "Authorization: Bearer ond_..."The tools this workflow uses: rank_imaging_priority, plan_event_imagery, predict_satellite_passes. See the full roster at /docs/mcp.
Go deeper
From headline to satellite evidence
One connected intelligence workflow across four surfaces — free to start, no GIS software or remote-sensing background required.