WordPress.org APIs

It is no secret that the WordPress Core interacts with remote APIs maintained by WordPress DOT org team. The most direct manifestation of these calls are plugin, theme and Core update notifications, and XML feeds in the Dashboard. Let’s take a look at all (hopefully) of the calls that WordPress is able to make, analyze the protocols and the data being exchanged.

WordPress.org APIs

Tools of the trade

In order to pinpoint remote request calls in any PHP web application, a combination of the following tools may be used:

  • grep – searching for calls to the WordPress HTTP API and rogue fsockopen, file_get_contents calls, etc.
  • strace running the built-in single-threaded PHP 5.4 web server to trace system calls
  • tcpdump
  • curl to forge and reforge requests quickly

Once a remote request is pinpointed, the data is dumped and analyzed, experimented with. Many times requests are scheduled and are triggered hours, days, years apart, or just once in the lifetime of the application, while installing, for example. Tracing system calls and raw network traffic makes sure that all obfuscated calls in PHP are still seen.

Off we go…

Most of the remote calls are initiated via the WordPress HTTP API, which is defined in wp-includes/class-http.php. Several other parts of the core bypass the API and initiate their own connections at their own discretion.

Remote Salt API

Located at https://api.wordpress.org/secret-key/1.1/salt/ this API is contacted during setup to generate randomly unique salts. It’s a simple GET request and returns text data that is directly inserted into the configuration file (are you thinking what I’m thinking?). This happens here.

wp_version_check

Located at https://api.wordpress.org/core/version-check/1.6/, behavior is defined in wp-includes/update.php.

Sends over the PHP version, the locale, the MySQL version, the number of blogs, the number of users, and whether multisite is enabled or not and the local package (versions of WordPress in other languages, will be set to ‘ru_RU’ if downloaded the Russian version from https://ru.wordpress.org/, for example; is empty for en_US) via a simple GET request, like version=3.3.1&php=5.4.0&locale=en_US&mysql=5.1.61&local_package=&blogs=1&users=1&multisite_enabled=0.

The response received looks something like this:

array (
  'offers' => 
  array (
    0 => 
    array (
      'response' => 'upgrade',
      'download' => 'https://wordpress.org/wordpress-3.3.1.zip',
      'locale' => 'en_US',
      'packages' => 
      array (
        'full' => 'https://wordpress.org/wordpress-3.3.1.zip',
        'no_content' => 'https://wordpress.org/wordpress-3.3.1-no-content.zip',
        'new_bundled' => 'https://wordpress.org/wordpress-3.3.1-new-bundled.zip',
        'partial' => false,
      ),
      'current' => '3.3.1',
      'php_version' => '5.2.4',
      'mysql_version' => '5.0',
      'new_bundled' => '3.2',
      'partial_version' => false,
    ),
  ),
)

The sent data is obviously not missed out on, however, is not publicly available as far as I know. Would be nice to know the distribution of versions, powerful information of undoubtedly exquisite value.

wp_update_plugins

Defined in wp-includes/update.php, sends a serialized array of plugins and plugin versions over to https://api.wordpress.org/plugins/update-check/1.0/ as a POST request.

User agent is set to the WordPress version and host. WordPress returns a serialized array containing something like:

array (
  'akismet/akismet.php' => 
  stdClass::__set_state(array(
     'id' => '15',
     'slug' => 'akismet',
     'new_version' => '2.5.5',
     'url' => 'https://wordpress.org/extend/plugins/akismet/',
     'package' => 'https://downloads.wordpress.org/plugin/akismet.2.5.5.zip',
  )),
  'jetpack/jetpack.php' => 
  stdClass::__set_state(array(
     'id' => '20101',
     'slug' => 'jetpack',
     'new_version' => '1.2.2',
     'url' => 'https://wordpress.org/extend/plugins/jetpack/',
     'package' => 'https://downloads.wordpress.org/plugin/jetpack.1.2.2.zip',
  )),
)

To get back the latest version of a plugin the POST array of plugins has to have a {path}/{plugin-file}.php key. So by merely adding $plugins['jetpack/jetpack.php'] we get a response from the API with the latest version and URLs.

There are 27,000+ registered plugins, and everybody knows what this would do, right?

  $contents = file_get_contents("https://plugins.svn.wordpress.org");
  foreach ( explode("\n",$contents) as $site ) {
    $matches = array();
  if (!preg_match("#<a .*>(.*)</a>#U", $site, $matches)) continue;
  $plugin_name = $matches[1].str_replace("/", ".php", $matches[1]);
  $plugins[$plugin_name] = array();
}

This results in about 2MB of data sent, which the WordPress.org API processes in around 40 seconds, and returns around 500kb of data back, containing a lot of plugin names, versions, download URLs.

wp_update_themes

Very similar to the plugin updates call. Made to https://api.wordpress.org/themes/update-check/1.0/, behavior defined in wp-includes/update.php.

There are also information APIs available for themes and plugins. These live at https://api.wordpress.org/themes/info/1.0/ and https://api.wordpress.org/plugins/info/1.0/ and accept POST data with all sorts of interesting commands.

Check out wp-admin/includes/plugin-install.php for more information. Feel free to play around with the hot_tags API method.

wp_check_browser_version

The Browser Happy API is responsible for checking your browser version and letting you know that it’s out of date. A while ago, I mentioned Browser Happy in Tiny projects inspired by WordPress.

The user agent is sent 'useragent' => 'Mozilla/5.0 (X11; Linux i686) AppleWebKit/535.19 (KHTML, like Gecko) Chrome/18.0.1025.142 Safari/535.19' as a POST request. What happens next is defined in wp-admin/includes/dashboard.php.

You can use this API (don’t forget to cache!) to alert your visitors if their browsers suck are out of date. The request is sent https://api.wordpress.org/core/browse-happy/1.0/.

Send over the User-Agent string and get something of this kind back:

array(9) {
  'platform' =>
  string(5) "Linux"
  'name' =>
  string(6) "Chrome"
  'version' =>
  string(12) "1.0.1025.142"
  'update_url' =>
  string(28) "https://www.google.com/chrome"
  'img_src' =>
  string(49) "https://s.wordpress.org/images/browsers/chrome.png"
  'img_src_ssl' =>
  string(48) "https://wordpress.org/images/browsers/chrome.png"
  'current_version' =>
  string(2) "16"
  'upgrade' =>
  bool(true)
  'insecure' =>
  bool(false)
}

Hall of Fame

https://api.wordpress.org/core/credits/1.0/?version=$wp_version&locale=$locale returns the contributors to a specific WordPress versions and other credit information.

Handbook

https://api.wordpress.org/core/handbook/1.0/?function=$function_name&locale=$locale&version=$version&redirect=true provides links to https://phpdoc.wordpress.org/, if no version is provided trunk is assumed.

Final thoughts

Like any other API out there, the WordPress.org API can be mashed up with other APIs. Alas, the WordPress.org API documentation is far from useful (linked from https://api.wordpress.org/).

For those of you who are interested in disabling external HTTP calls in WordPress, you’ll be surprised at how easy it is to do so.

Do you know of any other APIs that WordPress.org exposes to the public? Do let me know, I’m very interested. Thanks for stopping by.