OSINT^:OSINT

Conflict & Environmental Monitoring

Event datasets, munitions identification, fire and deforestation feeds, and the tools built for documenting harm.

intermediateupdated 2026-10-03acled · gdelt · firms · sentinel-hub

What this covers

Two thematic areas with mature, purpose-built open datasets: armed conflict and environmental change. Both are unusual in OSINT for having authoritative structured data behind them rather than just tools, which moves the skill from finding sources to reading methodologies and querying them properly.

Method

  1. Start from a bounded question with a place and a date range. These are datasets, not search engines — a query without a bounding box and a window either returns nothing useful or exceeds your quota.
  2. Read the codebook before the data. What ACLED counts as an event, how FIRMS defines a thermal anomaly, what a Global Forest Watch alert confidence class means: each materially changes what a number means, and each is documented.
  3. Query the dataset rather than the dashboard. Dashboards aggregate in ways you cannot audit and cannot cite. Pull the records, store them with the retrieval date, and do your own aggregation.
  4. Establish the baseline before the anomaly. One fire detection at an industrial site means nothing until you know how often that pixel normally burns. Pull the same query for the previous year and the previous month.
  5. Corroborate across two independent feeds. Independence means different sensors or different collection methods — a VIIRS hotspot and a Sentinel-2 burn scar, an ACLED event and a dated photograph, a GDELT article cluster and a satellite image. Two aggregators that both scrape the same wire service are one source.
  6. For munitions, work from markings outward, and write “consistent with” unless you have stencilling, lot numbers or a fuze you can read. This is the single most common place open-source conflict reporting goes wrong.
  7. Record the evidentiary chain for each claim: the dataset and its version, the exact query, the retrieval timestamp, the hash of any imagery you downloaded, and the second source. A finding that cannot be re-run is not a finding.

Conflict

SourceWhat it gives you
ACLEDCoded conflict events — date, location, actors, event type, fatalities — globally, with a published methodology and an API. The reference dataset.
GDELTGlobal news coverage indexed by location, theme, tone and actor, updated every 15 minutes. Not events; coverage of events.
LiveuamapNear-real-time mapped events aggregated from social media. Fast but unverified; a lead generator only.
GeoconfirmedVolunteer-geolocated conflict media, each entry with the coordinates and the reasoning. Verified work you can audit.
Open Source Munitions PortalIdentification reference for munitions and their remnants, which is how you turn a photo of debris into a weapon type.
Bellingcat’s Civilian Harm datasetsVerified incident databases with the sourcing attached to each entry.

ACLED API

Coded event data with a documented methodology, which is what separates it from every aggregator in the table above. Each record carries a date, coordinates with a stated precision level, named actors, an event type and sub-event type, a fatality estimate, and the sources it was coded from.

The API moved and the auth model changed. The old api.acleddata.com host with key=/email= query parameters is gone — the hostname no longer resolves, and the key system was retired in September 2025. Current access is a bearer token from an OAuth password grant against your myACLED account, used against acleddata.com/api/.

# token: valid 24 hours, with a refresh token valid 14 days
TOKEN=$(curl -s -X POST 'https://acleddata.com/oauth/token' \
  -d 'username=you@example.com' --data-urlencode 'password=YOUR_PASSWORD' \
  -d 'grant_type=password' -d 'client_id=acled' -d 'scope=authenticated' \
  | jq -r .access_token)
# events in one country over a window, as JSON
curl -s -H "Authorization: Bearer $TOKEN" -G 'https://acleddata.com/api/acled/read' \
  --data-urlencode 'country=Ukraine' \
  --data-urlencode 'event_date=2026-01-01|2026-03-31' \
  --data-urlencode 'event_date_where=BETWEEN' --data-urlencode 'limit=0' \
  | jq '.count'

# CSV straight to disk, which is what you archive
curl -s -H "Authorization: Bearer $TOKEN" -G 'https://acleddata.com/api/acled/read' \
  --data-urlencode '_format=csv' --data-urlencode 'country=Sudan' \
  --data-urlencode 'event_date=2026-01-01|2026-03-31' \
  --data-urlencode 'event_date_where=BETWEEN' --data-urlencode 'limit=0' \
  -o "acled-sudan-$(date -u +%F).csv"

# one event type only — the sub-event taxonomy is where the precision is
curl -s -H "Authorization: Bearer $TOKEN" -G 'https://acleddata.com/api/acled/read' \
  --data-urlencode 'country=Nigeria' \
  --data-urlencode 'event_type=Explosions/Remote violence' \
  --data-urlencode 'event_date=2026-01-01|2026-03-31' \
  --data-urlencode 'event_date_where=BETWEEN' | jq '.data | length'

# a named actor's events, for an order-of-battle or attribution question
curl -s -H "Authorization: Bearer $TOKEN" -G 'https://acleddata.com/api/acled/read' \
  --data-urlencode 'actor1=Military Forces of Sudan (2019-)' \
  --data-urlencode 'actor1_where=%3D' --data-urlencode 'limit=200' | jq '.count'

# narrow to an admin region, which is how you bound to a locality
curl -s -H "Authorization: Bearer $TOKEN" -G 'https://acleddata.com/api/acled/read' \
  --data-urlencode 'country=Mali' --data-urlencode 'admin1=Mopti' \
  --data-urlencode 'event_date=2026-01-01|2026-03-31' \
  --data-urlencode 'event_date_where=BETWEEN' | jq '.data | length'

# only the fields you need, which keeps responses small
curl -s -H "Authorization: Bearer $TOKEN" -G 'https://acleddata.com/api/acled/read' \
  --data-urlencode 'country=Ukraine' --data-urlencode 'limit=50' \
  --data-urlencode 'fields=event_date|event_type|sub_event_type|location|latitude|longitude|fatalities|source' \
  | jq '.data[0]'

# the deleted-records endpoint, so a stored copy can be reconciled with upstream revisions
curl -s -H "Authorization: Bearer $TOKEN" -G 'https://acleddata.com/api/deleted/read' \
  --data-urlencode 'deleted_timestamp=1735689600' | jq '.count'

The R package acledR and the acled Python package both wrap this and handle the token refresh; either is less work than scripting the grant yourself if you query regularly. Access is free for non-commercial use with registration, and the terms restrict redistribution of the raw data — cite and link rather than re-publishing the CSV.

Read the codebook before drawing any conclusion. Three things in particular: coordinate precision is a graded field, and a precision-3 event is located to an admin region rather than a point; fatality figures are the most conservative reported estimate, not a count; and event density tracks reporting density, so comparing two regions compares their journalism as much as their violence. The deleted endpoint exists because ACLED revises history as better sourcing arrives, which means a stored extract drifts from upstream.

GDELT DOC 2.0 API

Global news coverage, indexed every 15 minutes across languages, queryable by phrase, location, theme, tone and source country. It tells you what was reported, where and in what volume, which is a different and complementary question to what happened. No key, no registration.

# articles matching a phrase, geographically filtered, as JSON
curl -s -G 'https://api.gdeltproject.org/api/v2/doc/doc' \
  --data-urlencode 'query="artillery" sourcecountry:UP' \
  --data-urlencode 'mode=artlist' --data-urlencode 'format=json' \
  --data-urlencode 'maxrecords=100' --data-urlencode 'timespan=7d' \
  | jq '.articles[] | {seendate, domain, title, language}'

# coverage volume over time — the shape of the curve is the finding
curl -s -G 'https://api.gdeltproject.org/api/v2/doc/doc' \
  --data-urlencode 'query="port of berbera"' \
  --data-urlencode 'mode=timelinevol' --data-urlencode 'format=json' \
  --data-urlencode 'timespan=3m' | jq '.timeline[0].data[-10:]'

# an exact window rather than a rolling span
curl -s -G 'https://api.gdeltproject.org/api/v2/doc/doc' \
  --data-urlencode 'query="chemical plant" sourcelang:eng' \
  --data-urlencode 'mode=artlist' --data-urlencode 'format=json' \
  --data-urlencode 'startdatetime=20260301000000' \
  --data-urlencode 'enddatetime=20260315000000' | jq '.articles | length'

# which countries are covering a story, and which are not
curl -s -G 'https://api.gdeltproject.org/api/v2/doc/doc' \
  --data-urlencode 'query="grain corridor"' \
  --data-urlencode 'mode=timelinesourcecountry' --data-urlencode 'format=json' \
  --data-urlencode 'timespan=1m' | jq '[.timeline[].series] | .[0:8]'

# tone distribution, for spotting a coordinated framing shift
curl -s -G 'https://api.gdeltproject.org/api/v2/doc/doc' \
  --data-urlencode 'query="sanctions relief"' \
  --data-urlencode 'mode=tonechart' --data-urlencode 'format=json' \
  --data-urlencode 'timespan=2w' | jq '.tonechart'

# images published alongside the coverage, which sometimes surface the source photo
curl -s -G 'https://api.gdeltproject.org/api/v2/doc/doc' \
  --data-urlencode 'query="refinery fire"' \
  --data-urlencode 'mode=imagecollageinfo' --data-urlencode 'format=json' \
  --data-urlencode 'timespan=3d' | jq '.images[0:5]'

The DOC API serves a rolling window — roughly the last three months by default, reaching back to 1 January 2017 with explicit datetimes — and caps at 250 records per request for article and image modes. For anything longer or larger, the GDELT Event and GKG tables are published on BigQuery and as raw CSV, which is the right route for a multi-year series.

What it does not tell you: GDELT indexes articles, not events, so one incident covered by four hundred outlets is four hundred records. A volume spike measures attention. Syndication means the same wire copy appears under many domains, which is why a GDELT cluster and an aggregator that scrapes the same wires are not two independent sources.

NASA FIRMS

Active fire and thermal anomaly detections from MODIS and VIIRS, available within three hours of satellite overpass. Thermal detection is a far broader OSINT primitive than wildfire monitoring: gas flaring, industrial process heat, shelling, burning buildings and crop residue all register.

Get a free MAP_KEY from firms.modaps.eosdis.nasa.gov/api/map_key.

export MAP_KEY='your-map-key'
# check the key works and how much of your quota is left
curl -s "https://firms.modaps.eosdis.nasa.gov/mapserver/mapkey_status/?MAP_KEY=$MAP_KEY"

# detections in a bounding box over the last day — order is west,south,east,north
curl -s "https://firms.modaps.eosdis.nasa.gov/api/area/csv/$MAP_KEY/VIIRS_SNPP_NRT/30.0,46.0,36.0,50.0/1"

# a specific past date, with the day range starting from it (1-10 days)
curl -s "https://firms.modaps.eosdis.nasa.gov/api/area/csv/$MAP_KEY/VIIRS_NOAA20_NRT/30.0,46.0,36.0,50.0/2/2026-03-11" \
  -o firms-2026-03-11.csv

# the archival standard-quality product, for anything older than the NRT window
curl -s "https://firms.modaps.eosdis.nasa.gov/api/area/csv/$MAP_KEY/VIIRS_SNPP_SP/30.0,46.0,36.0,50.0/5/2025-07-01" \
  -o firms-archive.csv

# which products cover which dates, so you query one that exists
curl -s "https://firms.modaps.eosdis.nasa.gov/api/data_availability/csv/$MAP_KEY/all"

# filter to high-confidence daytime detections at a site
curl -s "https://firms.modaps.eosdis.nasa.gov/api/area/csv/$MAP_KEY/VIIRS_SNPP_NRT/30.0,46.0,36.0,50.0/1" \
  | awk -F, 'NR==1 || ($10=="h" || $10>=80)'

# a year of the same small box, to build the baseline before you call an anomaly
for d in 2025-0{1..9}-01 2025-1{0..2}-01; do
  curl -s "https://firms.modaps.eosdis.nasa.gov/api/area/csv/$MAP_KEY/VIIRS_SNPP_SP/30.0,46.0,36.0,50.0/10/$d"
  sleep 2
done > baseline.csv

The quota is 5,000 transactions per 10-minute window per key, and a large bounding box counts as several, so loop with a sleep. Day range per request is 1–10. VIIRS at 375 m resolves far more than MODIS at 1 km and is what you want for anything at a single site; the _NRT products are the fast ones, _SP the reprocessed archive.

Reading a detection: confidence is l/n/h for VIIRS and a 0–100 percentage for MODIS, frp is fire radiative power in megawatts, daynight matters because daytime detections suffer more false positives from reflective surfaces, and scan/track give the actual pixel footprint — the detection is a pixel, not a point, so the coordinates are the pixel centre and the real event is somewhere within a few hundred metres of it.

A hotspot is a thermal anomaly and nothing more. Gas flares burn continuously and appear every single night, which is why the baseline query above matters: the finding is not “a hotspot at this site”, it is “a hotspot at a site that produced none in the preceding twelve months”.

Sentinel Hub on the Copernicus Data Space Ecosystem

Free Sentinel-1, Sentinel-2 and Sentinel-3 imagery, both as an interactive browser and as an API. Use the browser to find the scene and settle the band combination; use the API to pull the exact same thing reproducibly, with a date and a bounding box you can cite.

Access moved to the Copernicus Data Space Ecosystem, which replaced both the old Copernicus Open Access Hub and the commercial Sentinel Hub free tier for Sentinel data. Register at dataspace.copernicus.eu, create an OAuth client in your account settings, and you get a monthly processing-unit quota at no cost.

Start in EO Browser: set the area, set the date range, pick Sentinel-2 L2A, and step through the available passes. The band combinations that make change visible where true colour hides it:

Burn scars (Sentinel-2):        B12, B8A, B4   — recent burns go red-brown, vegetation green
Vegetation health (NDVI):       (B8 - B4) / (B8 + B4)
Normalised burn ratio (NBR):    (B8 - B12) / (B8 + B12), differenced pre/post for severity
Water and flooding:             B8A, B11, B4   — water goes near-black, wet soil distinct
Bare soil / earthworks:         B11, B8, B2    — fresh excavation and berms separate from vegetation
Smoke and plumes:               true colour, then B12/B8A/B4 to see through thin smoke
Sentinel-1 radar (SAR):         VV/VH, for cloud cover, night, and detecting hulls at sea

Then reproduce it through the API:

# token, valid about ten minutes
TOKEN=$(curl -s -X POST \
  'https://identity.dataspace.copernicus.eu/auth/realms/CDSE/protocol/openid-connect/token' \
  -H 'content-type: application/x-www-form-urlencoded' \
  -d 'grant_type=client_credentials' -d 'client_id=YOUR_CLIENT_ID' \
  --data-urlencode 'client_secret=YOUR_CLIENT_SECRET' | jq -r .access_token)

# which Sentinel-2 passes exist over this box in this window, and how cloudy
curl -s -X POST 'https://sh.dataspace.copernicus.eu/api/v1/catalog/1.0.0/search' \
  -H "Authorization: Bearer $TOKEN" -H 'Content-Type: application/json' \
  -d '{"collections":["sentinel-2-l2a"],
       "bbox":[30.0,46.0,30.3,46.3],
       "datetime":"2026-03-01T00:00:00Z/2026-03-20T00:00:00Z",
       "limit":20,
       "fields":{"include":["id","properties.datetime","properties.eo:cloud_cover"]}}' \
  | jq '.features[] | {id, datetime:.properties.datetime, cloud:.properties["eo:cloud_cover"]}'

# render a false-colour burn-scar image for one date, as a GeoTIFF you can hash
curl -s -X POST 'https://sh.dataspace.copernicus.eu/api/v1/process' \
  -H "Authorization: Bearer $TOKEN" -H 'Content-Type: application/json' \
  -d '{"input":{"bounds":{"bbox":[30.0,46.0,30.3,46.3]},
        "data":[{"type":"sentinel-2-l2a",
                 "dataFilter":{"timeRange":{"from":"2026-03-11T00:00:00Z","to":"2026-03-12T00:00:00Z"}}}]},
       "output":{"width":1024,"height":1024,"responses":[{"identifier":"default","format":{"type":"image/tiff"}}]},
       "evalscript":"//VERSION=3\nfunction setup(){return {input:[\"B12\",\"B8A\",\"B04\"],output:{bands:3}}}\nfunction evaluatePixel(s){return [s.B12*2.5, s.B8A*2.5, s.B04*2.5]}"}' \
  -o burnscar-2026-03-11.tiff

Hash the GeoTIFF the moment you download it and record the scene ID, the exact bounding box and the acquisition datetime. That tuple is what makes the imagery claim reproducible — “a Sentinel-2 image from around then” is not a citation.

Quotas are monthly processing units and are replenished on the first of the month; a 1024-pixel request is cheap, a full-resolution multi-band pull over a province is not. Sentinel-2 revisits every five days at the equator and the optical bands see nothing through cloud, which is why Sentinel-1 radar is the fallback for a specific date you cannot miss.

Global Forest Watch Data API

Deforestation and fire alerts as a queryable dataset rather than a dashboard. The integrated alerts layer combines three independent alert systems (GLAD-L, GLAD-S2 and RADD radar), which makes it one of the few places where cross-sensor corroboration is already built in and labelled.

Create an account, exchange it for a token, then mint an API key; the key goes in an x-api-key header. Without one, every query returns a 401 telling you so.

# sign up and get a short-lived token
curl -s -X POST 'https://data-api.globalforestwatch.org/auth/token' \
  -d 'username=you@example.com' --data-urlencode 'password=YOUR_PASSWORD' | jq -r .data.access_token

# mint an API key with that token
curl -s -X POST 'https://data-api.globalforestwatch.org/auth/apikey' \
  -H "Authorization: Bearer $GFW_TOKEN" -H 'Content-Type: application/json' \
  -d '{"alias":"osint-research","organization":"research","email":"you@example.com","domains":[]}' \
  | jq -r .data.api_key
export GFW_KEY='your-api-key'

# what datasets exist, and what each version is called
curl -s 'https://data-api.globalforestwatch.org/datasets' \
  | jq -r '.data[].dataset' | grep -E 'integrated_alerts|viirs|burned'

# daily integrated deforestation alerts for one country
curl -s -H "x-api-key: $GFW_KEY" -G \
  'https://data-api.globalforestwatch.org/dataset/gadm__integrated_alerts__iso_daily_alerts/latest/query/json' \
  --data-urlencode "sql=SELECT iso, gfw_integrated_alerts__date, SUM(alert__count) AS alerts
                    FROM results WHERE iso='BRA' AND gfw_integrated_alerts__date >= '2026-02-01'
                    GROUP BY iso, gfw_integrated_alerts__date ORDER BY 2 DESC" | jq '.data[0:5]'

# down to a second-level administrative area, which is a usable locality
curl -s -H "x-api-key: $GFW_KEY" -G \
  'https://data-api.globalforestwatch.org/dataset/gadm__integrated_alerts__adm2_daily_alerts/latest/query/json' \
  --data-urlencode "sql=SELECT adm1, adm2, SUM(alert__count) AS alerts FROM results
                    WHERE iso='COL' AND gfw_integrated_alerts__date >= '2026-01-01'
                    GROUP BY adm1, adm2 ORDER BY alerts DESC LIMIT 20" | jq '.data[0:5]'

# alert confidence, which is where the cross-sensor corroboration lives
curl -s -H "x-api-key: $GFW_KEY" -G \
  'https://data-api.globalforestwatch.org/dataset/gadm__integrated_alerts__adm2_daily_alerts/latest/query/json' \
  --data-urlencode "sql=SELECT gfw_integrated_alerts__confidence, SUM(alert__count) FROM results
                    WHERE iso='PER' GROUP BY 1" | jq '.data'

# VIIRS fire alerts from the same API, so fire and forest loss share one query layer
curl -s -H "x-api-key: $GFW_KEY" -G \
  'https://data-api.globalforestwatch.org/dataset/gadm__viirs__adm2_daily_alerts/latest/query/json' \
  --data-urlencode "sql=SELECT adm1, adm2, SUM(alert__count) FROM results
                    WHERE iso='IDN' AND alert__date >= '2026-02-01' GROUP BY 1,2 ORDER BY 3 DESC LIMIT 10" \
  | jq '.data[0:5]'

# which fields a dataset actually has, before you guess a column name
curl -s -H "x-api-key: $GFW_KEY" \
  'https://data-api.globalforestwatch.org/dataset/gadm__integrated_alerts__adm2_daily_alerts/latest/fields' \
  | jq -r '.data[].name'

# CSV instead of JSON, for a spreadsheet handover
curl -s -H "x-api-key: $GFW_KEY" -G \
  'https://data-api.globalforestwatch.org/dataset/gadm__integrated_alerts__iso_daily_alerts/latest/query/csv' \
  --data-urlencode "sql=SELECT * FROM results WHERE iso='BRA' LIMIT 1000" -o gfw-bra.csv

Versions are dated (v20261003), and latest resolves to the newest — pin the explicit version in anything you cite, because latest moves under you. A highest confidence integrated alert means more than one of the three systems detected the same loss, which is the built-in second source; a low confidence alert is a single-sensor detection and should be treated as a lead.

An alert is tree-cover loss, not a crime. Legal logging concessions, plantation harvest cycles, storm damage and seasonal leaf-off all generate alerts. The question that turns an alert into a story is whether the polygon falls inside a protected area or a concession boundary, and that is a separate dataset join.

Open Source Munitions Portal

A searchable reference library of verified photographs of munitions and their remnants, built by Airwars and Armament Research Services. Web-only, no API, and the correct first stop when you have a photograph of debris and no identification. Over a thousand verified entries, weighted heavily towards the Middle East and Ukraine.

Search at osmp.ngo by munition category, country, date or free text, then work the comparison deliberately:

1. Measure first         find a scale reference in your photo — a hand, a boot, a kerb, a
                         standard brick — and estimate diameter and length before you
                         browse, so you are filtering rather than pattern-matching
2. Filter by category     bomb, rocket, artillery projectile, guided missile, submunition.
                         Getting the category right eliminates most of the library
3. Compare the parts     tail assembly and fin count, fuze well, lug spacing, nose profile,
                         body seams, driving band. These are discriminating; overall shape
                         is not
4. Read the stencilling  alphanumeric markings, lot numbers, factory codes, colour bands.
                         A legible lot number is the only thing that gets you to a
                         production batch and a country of manufacture
5. Check the remnant      fragmentation pattern and wall thickness distinguish families
                         that look identical intact
6. Cross-reference        CAT-UXO for explosive ordnance detail and Bulletpicker's
                         manual archive for the original technical drawings

Record the OSMP entry ID for every comparison you made, including the ones you rejected, and the specific features you matched on. “Consistent with a 9M27K rocket on the basis of fin count and motor diameter, per OSMP entry NNNN” is defensible. “A cluster munition” is not, unless you can read the markings.

The geographic bias is a real limitation: a munition used outside the Middle East or Ukraine may simply be absent from the library, and absence is not evidence of a different weapon. Pair OSMP with CAT-UXO and Bulletpicker’s scanned ordnance manuals, which have broader scope and worse search.

Corroborating one event across two independent feeds

The single most useful habit in this area, and the thing that makes a claim stand up. “Independent” means a different sensor or a different collection route, not a different website.

Claim      a munitions strike on a named facility, 11 March 2026, from a social post
Source A   ACLED event: query country + admin1 + date, with event_type
           Explosions/Remote violence. Returns a coded event, its own sources, a
           coordinate precision level and a fatality estimate
Source B   NASA FIRMS: VIIRS detections in a tight box around the facility for that
           date and the day after. A thermal anomaly at the right pixel is an
           independent sensor observation, not a report
Source C   Sentinel-2 via CDSE: the first clear pass after the date, false-colour
           B12/B8A/B4. A new burn scar or structural change on imagery acquired after
           and absent before is the third, strongest leg
Baseline   the same FIRMS box for the preceding year, to show the pixel does not
           routinely register; the preceding Sentinel-2 pass, to show the scar is new
Negative   GDELT timelinevol for the facility name, to see whether coverage preceded
           the imagery — coverage before the satellite pass means the reporting is not
           derived from the imagery and is genuinely separate
Record     per leg: dataset and pinned version, exact query, retrieval timestamp,
           SHA-256 of any downloaded raster, and the scene ID with its acquisition time

Two legs from the same family is one leg: ACLED and Liveuamap both code from media reporting, GDELT and a news aggregator both index the same wires, MODIS and VIIRS are different sensors but can be on the same platform pass. A sensor observation plus a reported event is a genuine pair, and imagery plus a sensor observation is better.

State the negative result as explicitly as the positive one. “No VIIRS detection in a 2 km box on either date, and the first clear Sentinel-2 pass is nine days later” is a useful finding about the limits of what can be established, and publishing it is what distinguishes this work from advocacy.

Environment

SourceWhat it gives you
Global Forest WatchDeforestation alerts and tree-cover change, near-real-time, with an API behind it.
NASA FIRMSActive fire and thermal anomaly detections, updated within three hours of overpass.
Global Fishing WatchApparent fishing effort and vessel-behaviour events inferred from AIS. See transport tracking for its API.
Copernicus BrowserFree Sentinel-1/2/3 imagery with band combinations for burn scars, water, vegetation and earthworks.
UNOSATUN satellite analyses of humanitarian emergencies, already interpreted and published with methodology.
Resource Watch300+ environmental and human-wellbeing datasets, several of them real-time.

Tool reference

ToolWhat it doesCost
ACLED (Armed Conflict Location & Event Data Project)ACLED provides data and analysis on political violence and protest around the world, facilitating research, policy making, and journalistic reporting.partly free
AllTrailsAllTrails.com is a tool for discovering hiking, biking, and running trails worldwide, providing detailed trail maps, user reviews, and navigation support…partly free
Amazonia Socio Ambiental (RAISG)Amazon rainforest maps and shapefiles of natural protected areas, concessions, indigenous territories, oil, mining, roads, fires, deforestation in…free
Aqueduct Water Risk AtlasThe Aqueduct water Risk Atlas, developed by the World Resources Institute (WRI), is an interactive platform for assessing water-related risks globally.free
BirdNetIdentify bird sounds - find bird sounds on a global map.free
BulletpickerBulletpicker.com is a collection of ammunition guidebooks and manuals from several different armed forces.free
CITES Trade DatabaseAround 23 million records of trade in wildlife since 1975.free
CryO ToolsScientific tools for investigating the cryosphere (areas with snow & ice)free
EIA Global Environmental Crime TrackerMap/tracker of environmental crimes including trade in ivory, rhino, big cats, and other exotic animals.free
Environmental Justice AtlasMap of environmental-related conflict globallyfree
Global Monitoring System - ECOSOLVEIllicit online wildlife markets data from over 30 countries and regionsfree
Google Flood HubA visual tool to monitor river levels and forecast floods based on AI models developed by Google Research.free
Locust HubA repository for desert locust data with maps and other resources for tracking movements, early detection and planning locust control interventions.free
MerlinIdentify birds (visually), through an app.free
MovebankPlatform for animal tracking data.free
Nullschool Earth MapView current and historic wind, weather, ocean and pollution conditions on an interactive animated map.partly free
Police Records Access ProjectA database providing searchable access to California law enforcement records including police use-of-force incidents, shootings, and misconduct cases.free
Resource WatchA free open-data platform that hosts 300+ datasets on different topics relating to the environment and human well-being, including real-time datasets.free
River Runner GlobalCalculate which water stream a drop of rain will followfree
Species+Centralized website with vulnerable species information.free
UNOSAT AnalysesUNOSAT Analyses is a tool that maps humanitarian emergencies across the globe utilising United Nations Satellite Centre data.free
WildEyeTracking tool for data on environmental and wildlife crime cases, including court cases and convictions, across the globe.free
Wildlife Trade PortalAn open-source tool to search wildlife seizure data worldwide.free
WildMe & WildBookOpen source pattern recognition software to identify unique whales, sharks, zebras, jaguars, skunks, fish and much more.free
World Database on Protected and Conserved AreasA comprehensive global database on terrestrial and marine protected areas. Also known as Protected Planet.free
CAT UXOA repository for professionals working in the explosive ordnance disposal (EOD) space.partly free

Pitfalls

  • Event datasets reflect reporting, not reality. Areas with more journalists produce more recorded events. Never read event counts as a direct measure of violence.
  • Real-time aggregators are unverified. Liveuamap and similar repost claims. Verify before use.
  • Thermal detections have mundane causes. Flaring, agricultural burning and industrial process heat all look alike from orbit.
  • Deforestation alerts include legal logging and seasonal change. Alert ≠ crime.
  • Documenting harm carries duty of care. Graphic material needs handling policies, and identifying victims or witnesses can endanger them. Minimise what you publish.

Worked example

One datum: a coordinate and a date. 46.482, 30.724 on 11 March 2026, from a post claiming a strike on a grain terminal. The coordinate is real and the returns below are illustrative; what matters is which query answers which part of the claim.

# 1. was anything coded as an event there
TOKEN=$(curl -s -X POST 'https://acleddata.com/oauth/token' \
  -d 'username=you@example.com' --data-urlencode 'password=PW' \
  -d 'grant_type=password' -d 'client_id=acled' -d 'scope=authenticated' | jq -r .access_token)

curl -s -H "Authorization: Bearer $TOKEN" -G 'https://acleddata.com/api/acled/read' \
  --data-urlencode 'country=Ukraine' --data-urlencode 'admin1=Odesa' \
  --data-urlencode 'event_date=2026-03-10|2026-03-13' \
  --data-urlencode 'event_date_where=BETWEEN' \
  --data-urlencode 'fields=event_date|sub_event_type|location|latitude|longitude|geo_precision|fatalities|source' \
  | jq '.data[]'
# one event, 2026-03-11, sub_event_type "Shelling/artillery/missile attack",
# geo_precision 2, coordinates the city centroid rather than the terminal

Geo-precision 2 means ACLED located this to a town, not a point — so it corroborates that something happened in Odesa that day, and says nothing about this coordinate.

# 2. an independent sensor: thermal detections in a tight box around the terminal
curl -s "https://firms.modaps.eosdis.nasa.gov/api/area/csv/$MAP_KEY/VIIRS_SNPP_NRT/30.70,46.46,30.75,46.50/2/2026-03-11"
# two detections, 2026-03-11 ~22:40 UTC, frp 4.8 and 2.1, confidence n, daynight N

A night-time detection with that radiative power inside a 4 km box is a real heat source, and nothing about it came from a news report.

# 3. the baseline — does this pixel burn routinely
for d in 2025-03-01 2025-06-01 2025-09-01 2025-12-01 2026-01-01 2026-02-01; do
  curl -s "https://firms.modaps.eosdis.nasa.gov/api/area/csv/$MAP_KEY/VIIRS_SNPP_SP/30.70,46.46,30.75,46.50/10/$d" | tail -n +2
  sleep 2
done | wc -l
# 0 — no detections in that box across sixty sampled days of the preceding year

That is what converts the hotspot from an observation into an anomaly.

# 4. imagery: find the first clear pass after the date
TOK=$(curl -s -X POST \
  'https://identity.dataspace.copernicus.eu/auth/realms/CDSE/protocol/openid-connect/token' \
  -H 'content-type: application/x-www-form-urlencoded' \
  -d 'grant_type=client_credentials' -d 'client_id=ID' \
  --data-urlencode 'client_secret=SECRET' | jq -r .access_token)

curl -s -X POST 'https://sh.dataspace.copernicus.eu/api/v1/catalog/1.0.0/search' \
  -H "Authorization: Bearer $TOK" -H 'Content-Type: application/json' \
  -d '{"collections":["sentinel-2-l2a"],"bbox":[30.70,46.46,30.75,46.50],
       "datetime":"2026-03-08T00:00:00Z/2026-03-20T00:00:00Z","limit":20,
       "fields":{"include":["id","properties.datetime","properties.eo:cloud_cover"]}}' \
  | jq '.features[] | {id, datetime:.properties.datetime, cloud:.properties["eo:cloud_cover"]}'
# 2026-03-09 cloud 3%, 2026-03-14 cloud 11% — a usable before/after pair

Pull both as B12/B8A/B4 GeoTIFFs through /api/v1/process, hash each, and record the scene IDs. The 14 March image shows a dark scar and a collapsed roof span on one silo where the 9 March image shows an intact structure.

What you can assert: a thermal anomaly at the coordinate on the night of 11 March, from a sensor and not a report; no comparable detection in that pixel across the preceding year; and structural change visible between two named Sentinel-2 scenes bracketing the date. Three legs, two of them independent of all media reporting, each tied to a query, a retrieval time and a hash.

What is not: the cause. Neither the hotspot nor the scar distinguishes a missile strike from a drone, an accident or deliberate arson, and attribution needs the remnant. If a photograph of debris surfaces, that goes to OSMP for a markings comparison, and the identification is stated as consistent-with until a lot number is legible.

What would falsify it: a second pre-event image showing the roof already damaged, a FIRMS quality flag or geolocation error placing the detections outside the terminal, or a longer baseline showing routine industrial heat at the same pixel. The imagery pair carries the structural-change claim; the thermal detections only narrow the time window and do not identify a cause.

Broader catalogues

More tools

Further tools for this area from the OSINT Newsletter Tools Library (Conflict OSINT, Environment & Wildlife OSINT, Public Media OSINT), excluding those already listed above.

ToolWhat it does
1stHeadlinesNews aggregation and headline-monitoring site that brings together headlines from multiple news organisations in one searchable…
AllYouCanReadA global directory of newspapers, magazines and news websites covering more than 200 countries.
BamqamA real-time geopolitical intelligence dashboard that aggregates military activity and conflict data into a map-based interface.
Combat AircraftMilitary aviation reference database providing aircraft specifications, operators, imagery, aviation news, and background…
Combined IUU Vessel ListA free, searchable database that consolidates official Illegal, Unreported and Unregulated (IUU) fishing vessel lists published…
Country StudiesA free online reference library providing detailed country profiles covering history, politics, society, economics, security…
ECO-SOLVE Global Monitoring SystemAn OSINT and AI-enabled monitoring platform for identifying and analysing online environmental crime, particularly illegal…
EJAtlas (Environmental Justice Atlas)A global, collaborative database that documents environmental conflicts and environmental justice struggles.
Europe Media MonitorAn automatic system that monitors global news media in near real time, aggregating multilingual news, alerts and trend analysis…
GeoconfirmedA collaborative OSINT platform to help analysts verify and geolocate images, videos, and events from conflicts worldwide.
OSNT.INAn AI-powered daily intelligence brief on the Russia-Ukraine war, synthesized from 200+ Ukrainian and Russian-language open…

Sources

Both catalogues below are maintained by other people and are considerably larger than this page. Use them as the canonical index; this sheet is a working route through them.

Neither publishes a licence, so nothing here is copied from them: tool names, one-line descriptions, cost flags and links are catalogue facts, and the method and commentary are this site’s own. See credits.

On this page

☧ CPTS · ordered posts

Locked

Unlock with your Main_Key through local GPG.

Start the local GPG helper

In your cheatsheet checkout, run:

npm run unlock:helper

Open the link it prints, or paste its pairing code below. Keep the terminal open until GPG finishes.

Import an index you already decrypted

Download the encrypted index, then decrypt with GPG:

gpg --output report-index.json --decrypt report-index.gpg

Your private key stays in GPG. The decrypted index is saved in this browser so you can reopen it after navigation, refreshes and restarts. Lock this browser or clear this site's browser data to remove the saved index. Nothing is uploaded.