Saved queries and change alerts

Save an Overpass query with a name. We run it on a schedule and store its latest result. Each saved query has a URL that returns that stored result, and you can get an e-mail or a webhook call when the result changes. Available on the Medium plan (20 saved queries) and the Large plan (100).

Run once, read many times

All charging stations of Germany, with their tags and a centre point:

[out:json][timeout:300];
area["ISO3166-1"="DE"][admin_level=2]->.c;
nwr["amenity"="charging_station"](area.c);
out center tags;

Running this query through the interpreter takes about a minute and returns 47,000 elements, 19 MB. Save it as charging-de with a daily run, and reading the stored result takes 0.15 s, or 25 ms when the client accepts gzip (2.6 MB). Measured on the Medium plan on 29 October 2026:

TimeSizeCounts as
POST /v1/KEY/interpreter60 s19 MBone request, and the query runs each time
GET /v1/KEY/q/charging-de0.15 s19 MBone request; the query does not run
the same with gzip0.025 s2.6 MB

If 1,000 users each triggered this query, that would be up to 1,000 minutes of query time, and each user would wait for their own run. With a saved query, the query runs once per scheduled run and the users read the stored result.

This costs no extra requests. A read counts as one request, the same as sending the query yourself; the scheduled run adds one request per run. The difference is speed: a read returns the stored result at once, the query itself does not run again.

What it is for

  • An app or a website that shows the same data to many users, such as all charging stations of a city. The query runs once per schedule; each user reads the stored result.
  • Queries that take minutes, such as a country-wide extract. Clients read the last successful result; they do not wait for a run.
  • Monitoring OpenStreetMap changes: a shop list that must stay current, a region a mapper looks after, a tag used by a data pipeline. When elements are added, removed or edited, you get an e-mail or a webhook call that names them.

Save a query

Open Saved queries in your dashboard and enter:

  • A name, used in the result URL: lower-case letters, digits and dashes, such as charging-stations.
  • The query, as you would send it to the interpreter. With [out:json] the alerts can name the elements; XML and CSV work too, then an alert says only that the result changed.
  • The schedule: every hour, every 6 hours, every day, every week, or only when you press Run now.
  • Whether you want an e-mail when the result changes, and a webhook URL.

The first run starts within a minute of saving. Editing the query starts a new run at once. A run uses a key of your account, counts as one request of your plan and keeps to its limits (query time, memory). Results can be up to 32 MB. If a run fails, the previous successful result stays available and the dashboard shows the error.

Read the result

curl https://api.overpass.ch/v1/YOUR_API_KEY/q/charging-stations

The result uses the output format set in the query ([out:json], [out:csv], XML). The response carries the header X-Saved-Query-Run with the time of the run, an ETag for If-None-Match, and gzip when the client accepts it. Each read counts as one request, the same as the query itself would, and is subject to the per-minute limit of your plan. A key of the type "Maps only" cannot read saved queries.

In JavaScript:

const r = await fetch("https://api.overpass.ch/v1/YOUR_API_KEY/q/charging-stations");
const data = await r.json();
console.log(data.elements.length, "stations, data of", r.headers.get("X-Saved-Query-Run"));

Error responses: 404 not_found for a name you have not saved, 503 no_answer_yet while no run has succeeded yet (the dashboard shows the error of a failed run), 403 no_saved_queries on a plan without saved queries.

Change alerts

After each run we compare the new result with the previous one. For JSON results we report the elements that were added, removed or changed; an element counts as changed when its tags or its geometry differ. For XML and CSV results we compare the complete output without the timestamp line, and report only that it changed.

The e-mail lists up to 100 elements of each kind with their names and links to openstreetmap.org. The webhook is a POST with this JSON body:

{
  "event": "query.changed",
  "query": "charging-stations",
  "title": "EV charging stations in Zürich",
  "run_at": "2026-10-29T06:00:12Z",
  "elements": 418,
  "added": [{"type": "node", "id": 11860345120, "name": "Migrol"}],
  "removed": [],
  "changed": [{"type": "way", "id": 1066552231, "name": "Parkhaus Urania"}],
  "added_count": 1, "removed_count": 0, "changed_count": 1,
  "summary": "1 added, 1 changed, 418 in all",
  "answer": "https://api.overpass.ch/v1/YOUR_API_KEY/q/charging-stations",
  "dashboard": "https://overpass.ch/dashboard/queries/12"
}

The request carries the headers X-Overpass-Event: query.changed and X-Overpass-Signature: sha256=…. The signature is the HMAC-SHA256 of the body with the secret shown on the page of the query. Check it before you trust a call:

# Python (Flask)
import hmac, hashlib
def verify(request, secret):
    want = "sha256=" + hmac.new(secret.encode(), request.get_data(), hashlib.sha256).hexdigest()
    return hmac.compare_digest(want, request.headers.get("X-Overpass-Signature", ""))
// Node (Express, with express.raw())
const crypto = require("crypto");
function verify(req, secret) {
  const want = "sha256=" + crypto.createHmac("sha256", secret).update(req.body).digest("hex");
  return crypto.timingSafeEqual(Buffer.from(want), Buffer.from(req.get("X-Overpass-Signature") || ""));
}

Delivery: one call per detected change, with a 10 second timeout, no redirects, and only to public IP addresses. A failed delivery is not retried, so that change is lost for the webhook if your endpoint is down; the next detected change is delivered as a new call. The page of the query shows the status of the last delivery.

Examples

Charging stations of a city, for an app

Runs daily. The app reads the result URL; the alert reports stations added to or removed from the map.

[out:json][timeout:60];
area["name"="Zürich"]["admin_level"="8"]->.a;
nwr["amenity"="charging_station"](area.a);
out center tags;

Monitor the shops of a brand

Runs weekly. When a shop is added or removed on OpenStreetMap, the webhook posts the element to your system.

[out:json][timeout:120];
nwr["brand:wikidata"="Q115241"]["shop"];
out center tags;

Monitor fixme tags in a region

Runs every 6 hours over the region a mapper looks after. New or edited fixme notes arrive by e-mail with links to the elements.

[out:json][timeout:60];
nwr[~"^fixme$"~"."](47.30,8.40,47.45,8.65);
out center tags;

Read a large extract without running the query

A country-wide query that takes minutes runs daily on the Large plan. Clients read the stored result.

[out:csv(::id, name, "addr:city", website)][timeout:600][maxsize:1073741824];
area["ISO3166-1"="CH"][admin_level=2]->.c;
nwr["amenity"="pharmacy"](area.c);
out;

Limits

  • Medium keeps 20 saved queries, Large 100. Each runs at most once an hour.
  • A run counts as one request of your plan and runs with the query time and memory of the plan. A run that fails (timeout, error, result over 32 MB) keeps the previous result and shows the error on the page of the query.
  • The account needs an active key of the type "All APIs" created on the website; the runs use it.
  • Each read of a result counts as one request, the same as sending the query would, and is subject to the per-minute limit of your plan. The feature costs no extra requests; it saves the query time.