Overpass: “does not support the diff or adiff mode”
Why a history query with [diff:] or [adiff:] fails with JSON or CSV output, and two ways to get the changes.
[out:json] or [out:csv(…)] from a query with [diff:] or [adiff:], or write [out:xml], and read the XML. If your code needs JSON, run the query once for each date with [date:] and compare the two answers by type, id and version.
The error
[out:json][adiff:"2024-01-01T00:00:00Z","2025-01-01T00:00:00Z"]; nwr(47.3693,8.5385,47.3707,8.5405)(if:t["amenity"]=="restaurant"); out meta;
Overpass refuses this query before it runs, with HTTP 400 and an HTML page that says:
Error: line 1: static error: The selected output format does not support the diff or adiff mode.
[out:csv(…)] gets the same answer. A diff is a list of <action> elements with an old and a new version, and only the XML output has a form for that.
Fix 1: read the XML
Write [out:xml], or leave out the out setting; XML is the default. Python reads the answer with xml.etree.ElementTree from the standard library:
import xml.etree.ElementTree as ET
root = ET.fromstring(answer) # the bytes of the response
for action in root.findall("action"):
old, new = action.find("old/*"), action.find("new/*")
print(action.get("type"), (new if new is not None else action[0]).get("id"))
Each action is create, modify or delete. With [adiff:], a delete whose new version has visible="false" is a deleted element, and one with visible="true" still exists but no longer matches the query. OpenStreetMap changes in an area has the whole script.
Fix 2: two queries in JSON
Run the same query with [date:] for each of the two dates and compare the elements. With out meta each element has its version, so an edit shows as a change of version:
import json
import os
import urllib.parse
import urllib.request
URL = "https://api.overpass.ch/v1/" + os.environ["OVERPASS_CH_KEY"] + "/interpreter"
QUERY = """[out:json][timeout:60][date:"{date}"];
nw(47.369,8.541,47.376,8.547)(if:t["amenity"]=="restaurant");
out meta center;"""
def at(date):
data = urllib.parse.urlencode({"data": QUERY.format(date=date)}).encode()
els = json.loads(urllib.request.urlopen(URL, data=data, timeout=90).read())["elements"]
return {(e["type"], e["id"]): e for e in els}
a, b = at("2023-01-01T00:00:00Z"), at("2025-01-01T00:00:00Z")
for k in sorted(b.keys() - a.keys()):
print("added ", b[k]["tags"].get("name", k))
for k in sorted(a.keys() - b.keys()):
print("removed ", a[k]["tags"].get("name", k))
edited = [k for k in a.keys() & b.keys() if a[k]["version"] != b[k]["version"]]
print(len(edited), "edited,", len(a.keys() & b.keys()) - len(edited), "unchanged")
added Da Raffaele
added Rank
added Rank
added Wien Zeile
added Rising Sun
added Hirschli
added roots
added Restaurant zum weissen Kreuz
removed Enzian vegan food
removed Isebähnli
removed The Butcher
removed Kormasutra
removed Öpfelchammer
removed barfüsser
removed Bauernschänke
removed Long Wai
removed ('node', 9270127717)
removed ('node', 9870177417)
61 edited, 4 unchanged
The restaurants of the Niederdorf in Zurich from 2023 to 2025. Both queries together took 2.9 seconds. The totals are those of the [adiff:] query on the same area, but this way cannot tell a deleted restaurant from one that is now something else: both are only missing at the second date. To find out, ask for the missing ids at the second date, or use [adiff:].
Good to know
[date:]alone works with every output format, JSON and CSV included. Only[diff:]and[adiff:]need XML.- The history starts on 12 September 2012. A date before that returns only the elements that nobody edited since.
out metaadds the version, the time and the user of each element; without it, the JSON comparison above has no versions to compare.
On overpass.ch
History queries are part of the Medium and Large plans; other plans get HTTP 403 history_not_in_plan. Overpass history queries explains [date:], [diff:] and [adiff:] with examples.