Skip to content

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.