R3 — wordpress.org update APIs (live-verified Sept 2026)¶
All endpoints verified with live requests this session. No rate limits, no API key, no UA enforcement — but send an identifying User-Agent and cache (WP itself re-checks every 12h; our 1h cache is polite).
Core¶
GET https://api.wordpress.org/core/version-check/1.7/
- Latest version: offers[0].current (also == offers[0].version).
- offers[] has ~26 entries: latest + latest-security-release of every maintained
branch — do NOT take any element other than [0] for "latest".
- ?locale=xx_XX prepends a localized offer at [0] for that locale.
- php_version / mysql_version minimums available per offer.
Plugins — bulk (the right way)¶
POST https://api.wordpress.org/plugins/update-check/1.1/
- Content-Type: application/x-www-form-urlencoded; each FIELD is JSON-encoded:
plugins={"plugins":{"<folder>/<file>.php":{"Name":"...","Version":"<installed>"}},
"active":["<folder>/<file>.php"]}, translations=[], locale=["en_US"], all=true
- Response: plugins map = outdated (each has new_version, package, requires,
tested, requires_php); no_update map = current (same shape); keys absent from
BOTH = premium/not-on-wp.org.
- PHP empty maps serialize as [] not {} — handle both.
Themes — bulk¶
POST https://api.wordpress.org/themes/update-check/1.1/
- themes={"active":"<stylesheet>","themes":{"<slug>":{"Name":"...","Version":"..."}}}
(keyed by slug, NOT file path), translations=[], locale=["en_US"]
- Response shape: themes (outdated) / no_update (current), new_version per entry.
- The 1.0 theme endpoint is DEAD (400). The 1.0 plugin endpoint speaks PHP-serialize
and is obsolete — always use 1.1.
Per-item detail (optional)¶
GET https://api.wordpress.org/plugins/info/1.2/?action=plugin_information&request[slug]=<slug>
- Current version at version; 404 body {"error":"Plugin not found."} for premium.
- Trim payload: &request[fields][sections]=0&request[fields][versions]=0&request[fields][screenshots]=0
(101 KB -> 1.6 KB).
- last_updated is NOT ISO ("2026-08-31 3:07pm GMT") — don't parse with fromisoformat.
- Themes: GET .../themes/info/1.1/?action=theme_information&request[slug]=...
(version, ISO last_updated_time; no requires/tested).
Reference Python (stdlib-only, live-tested)¶
def latest_core_version(locale="en_US") -> str:
d = _get(f"{API}/core/version-check/1.7/?locale={urllib.parse.quote(locale)}")
return d["offers"][0]["current"]
def bulk_plugin_check(plugins: dict[str, str]): # {"elementor/elementor.php": "3.0.0"}
d = _post(f"{API}/plugins/update-check/1.1/", {
"plugins": json.dumps({"plugins": {f: {"Name": f.split("/")[0], "Version": v}
for f, v in plugins.items()},
"active": list(plugins)}),
"translations": "[]", "locale": json.dumps(["en_US"]), "all": "true"})
upd = {k: v["new_version"] for k, v in (d.get("plugins") or {}).items()}
cur = {k: v["new_version"] for k, v in (d.get("no_update") or {}).items()}
return upd, cur # absent keys = premium/unknown
def bulk_theme_check(themes: dict[str, str]): # {"twentytwentyfour": "1.0"}
d = _post(f"{API}/themes/update-check/1.1/", {
"themes": json.dumps({"active": next(iter(themes), ""),
"themes": {s: {"Name": s, "Version": v}
for s, v in themes.items()}}),
"translations": "[]", "locale": json.dumps(["en_US"])})
upd = {k: v["new_version"] for k, v in (d.get("themes") or {}).items()}
cur = {k: v["new_version"] for k, v in (d.get("no_update") or {}).items()}
return upd, cur
Citation: WP core wp-includes/update.php (master) — plugins/themes update-check are 1.1 (L464/L750); body encoding L451-455/L738-741.