How to call external APIs from a WordPress plugin

Isometric gateway console piping data from a cloud into a server block, with hook text Safe API Calls on the left.

When a Python notebook has to become part of a WordPress site, use wp_remote_get rather than cURL or file_get_contents. Radmila Mandzhieva wrote a piece on Towards Data Science arguing that data scientists should learn to call external APIs and read their documentation, with worked examples using REST Countries, JokeAPI and NASA’s APOD endpoint. I agree with the premise, but her examples are Python and run on a laptop. Put the same call inside a plugin on a live store and it fails in different ways.

Where the ported script breaks

The natural move is to copy the pattern. The notebook does requests.get(url).json(), so you reach for file_get_contents or a raw cURL call, then json_decode the result. Locally it works. On shared hosting it may not: some hosts block outbound HTTP, and a request with no timeout leaves the page hanging until PHP kills it whenever the remote API is down.

Two more problems show up later. Every page load hits the remote service, and free tiers throttle. NASA hands out DEMO_KEY for testing, and it isn’t built for production traffic. Sooner or later the API answers 400 or 401 with an error body, json_decode gives you null, and the breakage surfaces somewhere unrelated. The article’s own NASA example shows it, since combining date with end_date returns a 400 Bad Request with a JSON body listing the allowed fields. That’s a normal response, and your code has to plan for it.

How I call external APIs from a plugin

WordPress ships an HTTP API that papers over the transport differences between hosts, and wp_remote_get is the entry point. This is the shape I use for a read-only endpoint like REST Countries:

function bbioon_get_countries() {
    $cached = get_transient( 'bbioon_countries' );
    if ( false !== $cached ) {
        return $cached;
    }

    $response = wp_remote_get(
        'https://restcountries.com/v3.1/subregion/Central%20America',
        array( 'timeout' => 10 )
    );

    if ( is_wp_error( $response ) ) {
        return array();
    }

    if ( 200 !== wp_remote_retrieve_response_code( $response ) ) {
        return array();
    }

    $data = json_decode( wp_remote_retrieve_body( $response ), true );
    set_transient( 'bbioon_countries', $data, 12 * HOUR_IN_SECONDS );

    return $data;
}

is_wp_error catches the transport failing (DNS, a timeout, a blocked port) and the status check catches the API itself saying no. Those are different problems with different fixes. The transient matters as much as either: country data doesn’t change hourly, so a 12 hour cache means the page still renders when the API is down.

Keys are the other half. JokeAPI needs none and NASA wants the key as a query parameter, but most services accept an Authorization header, and headers don’t end up in access logs the way query strings do. When the API only takes the key in the URL, define it in wp-config.php rather than the plugin file, so a repo copy or a stack trace never carries it.

This is the kind of work I do regularly: a notebook or spreadsheet process someone depends on becomes a cached, error-handled part of the site. If you have one of those sitting around, I’m glad to look at it.

Read the docs for the errors

The habit worth stealing from the article is reading documentation with the failure examples in view. Most people copy the happy-path request and stop. The useful parts are the parameter constraints and the error responses: what NASA does when you mix date with end_date, or what the WooCommerce REST API returns for an invalid order ID, which newer versions reject outright. If you’re on the other side and writing your own endpoints, document the failing request next to the working one.

The question I still haven’t settled is where this code belongs. A helper in functions.php is where it usually ends up, but the same call in a must-use plugin survives a theme change, and clients change themes more often than they change data sources. I don’t have a clean rule. Wherever it lands, the error check and the transient go with it.

author avatar
Ahmad Wael
I'm a WordPress and WooCommerce developer with 15+ years of experience building custom e-commerce solutions and plugins. I specialize in PHP development, following WordPress coding standards to deliver clean, maintainable code. Currently, I'm exploring AI and e-commerce by building multi-agent systems and SaaS products that integrate technologies like Google Gemini API with WordPress platforms, approaching every project with a commitment to performance, security, and exceptional user experience.

Leave a Comment