agentsclimarketplace

Wp http api client

Skill Lonsdale201/wp-agent-skills/wordpress/wp-http-api-client

A community-maintained collection of agent skills for WordPress plugin and theme development.

Install
npx -y skills add Lonsdale201/wp-agent-skills --skill wp-http-api-client

Assembled from the repository path, not quoted from the project. Check it against their README if it does not work.

One thing to look at

  • 21 stars21 stars. Stars are a popularity signal and not a quality one, but at this level it is likely that nobody has read this closely except its author, and you would be relying on your own review.

What its author says it does

Copied from the file, not written here

Implement or audit outbound HTTP integrations in WordPress with wp_remote_request, wp_safe_remote_get/post/request, bounded timeouts, redirects and response sizes, host allowlists, JSON handling, authentication redaction, retries, idempotency, streaming downloads, and test hooks. Use when a plugin calls an external API, webhook destination, feed, license server, OAuth endpoint, remote file, private update service, remote report definition, or accepts a URL that WordPress fetches.

SKILL.md

10.4 KB, as published. Nobody here has run it

WordPress HTTP API Client

Use the WordPress HTTP API instead of cURL or URL-enabled filesystem calls. Core selects a transport, verifies TLS by default, follows WordPress proxy and debug hooks, and returns a consistent array|WP_Error contract.

Pick the request function

SituationFunction
Fixed, plugin-owned URLwp_remote_get/post/request() after an exact host configuration check
User/admin-influenced URLwp_safe_remote_get/post/request()
Large file downloaddownload_url() or stream => true with cleanup
Inbound webhookA REST route with signature/replay verification, not an HTTP client call

The wp_safe_remote_* variants set reject_unsafe_urls and validate the initial destination plus redirects with wp_http_validate_url(). Prefer them whenever any part of the URL is configurable.

A production JSON request

function myplugin_fetch_customer( int $customer_id ) {
    $base  = untrailingslashit( (string) get_option( 'myplugin_api_base' ) );
    $parts = wp_parse_url( $base );

    if ( 'https' !== ( $parts['scheme'] ?? '' )
         || 'api.example.com' !== strtolower( $parts['host'] ?? '' ) ) {
        return new WP_Error( 'myplugin_invalid_api_host', 'Invalid API host.' );
    }

    $response = wp_safe_remote_get(
        $base . '/v1/customers/' . rawurlencode( (string) $customer_id ),
        array(
            'timeout'             => 8,
            'redirection'         => 2,
            'limit_response_size' => 256 * KB_IN_BYTES,
            'headers'             => array(
                'Accept'        => 'application/json',
                'Authorization' => 'Bearer ' . myplugin_get_api_key(),
            ),
        )
    );

    if ( is_wp_error( $response ) ) {
        return new WP_Error(
            'myplugin_api_transport',
            __( 'The remote service could not be reached.', 'myplugin' ),
            array( 'cause' => $response->get_error_code() )
        );
    }

    $status = wp_remote_retrieve_response_code( $response );
    if ( 200 !== $status ) {
        return new WP_Error(
            'myplugin_api_status',
            __( 'The remote service returned an unexpected response.', 'myplugin' ),
            array( 'status' => $status )
        );
    }

    $content_type = (string) wp_remote_retrieve_header( $response, 'content-type' );
    if ( ! str_starts_with( strtolower( $content_type ), 'application/json' ) ) {
        return new WP_Error( 'myplugin_api_content_type', 'Expected JSON.' );
    }

    $data = json_decode( wp_remote_retrieve_body( $response ), true );
    if ( JSON_ERROR_NONE !== json_last_error() || ! is_array( $data ) ) {
        return new WP_Error( 'myplugin_api_json', 'Invalid JSON response.' );
    }

    return $data;
}

Validate the decoded schema before using values. An HTTP 200 and valid JSON do not prove the expected fields/types are present.

Keep executable policy out of remote responses

Classify every response field as data or control-plane input. A fixed HTTPS host and valid authentication do not justify executing whatever that host returns.

Do not let a response directly provide:

  • SQL passed to $wpdb->query(), get_results(), or a report executor;
  • PHP/template content, callback/class names, local paths, or capabilities;
  • an unrestricted plugin/theme update package URL;
  • a redirect, webhook destination, or secondary download host outside an exact semantic allowlist.

Keep report queries and privileged decisions local and versioned. Prefer a small remote vocabulary such as report_id, typed filter values, and feature states; map the ID to local code after strict schema/enum/range validation. A SELECT prefix check or SQL parser is not a durable sandbox, and read-only intent should also be enforced with database privilege separation where practical.

For private updates, validate metadata and package hosts independently, recheck every redirect, and verify a release signature/digest against a separately trusted key or manifest. TLS protects the channel but does not contain a compromised vendor. Apply wp-security-deep to rate the full SQL/update-to-code execution chain and its trigger.

Sending JSON

$response = wp_safe_remote_post( $url, array(
    'timeout'     => 8,
    'redirection' => 0,
    'headers'     => array(
        'Content-Type'  => 'application/json',
        'Accept'        => 'application/json',
        'Authorization' => 'Bearer ' . $token,
        'Idempotency-Key' => $operation_uuid,
    ),
    'body' => wp_json_encode( $payload, JSON_UNESCAPED_SLASHES ),
) );

Do not send a PHP array as body while declaring JSON; WordPress will otherwise form-encode it. Check wp_json_encode() failure for payloads that can contain invalid UTF-8.

SSRF boundary

An exact HTTPS host allowlist is the strongest application-level rule. Match the parsed host exactly, not with str_contains() or an unsafe suffix check that accepts api.example.com.attacker.test. If subdomains are required, accept the base host or a .-delimited suffix and still constrain scheme/port.

wp_safe_remote_* blocks many private/reserved destinations and revalidates redirects, but core URL validation is not a complete defense against every resolver, IPv6, DNS-rebinding, or hosting-network scenario. For arbitrary user-selected URLs, combine it with short limits and infrastructure egress controls/a fixed outbound proxy. Never disable the check through broad http_request_host_is_external or http_allowed_safe_ports filters.

Time, size, and redirect budgets

  • Set a short request-specific timeout; the default is not a product SLA.
  • Cap redirection, often at 0 for credential-bearing POSTs and 1-2 for GETs.
  • Set limit_response_size for bounded text/JSON responses.
  • For large files, stream to a temporary file rather than buffering in PHP.
  • Treat blocking => false as best-effort dispatch, not guaranteed delivery; no response body/status is available to prove remote acceptance.
  • Move slow/retriable integrations to Action Scheduler, WP-Cron, or a queue instead of blocking an admin/front-end request.

Errors, retries, and idempotency

Always handle both transport failure (WP_Error) and HTTP status. Do not leak raw provider bodies to users; they can contain internals or reflected input. Log a redacted request ID/status/error code, never Authorization/Cookie headers or complete personal-data payloads.

Retry only bounded, transient failures such as selected network errors, 429, and some 5xx responses. Honor Retry-After where practical and use backoff with jitter. Never automatically retry a non-idempotent write unless the provider supports an idempotency key or the operation has another deduplication gate.

Credentials and privacy

  • Prefer Connectors, environment variables, or wp-config.php constants for production credentials; DB settings are sometimes required for admin UX.
  • Never put secrets in URLs, because URLs leak into logs and caches.
  • Require explicit feature/admin intent before spending quota or sending site content. A configured site-wide connector is not consent for every plugin.
  • Document personal-data transfers and retention; run the privacy skill.

Downloads and cleanup

download_url() uses wp_safe_remote_get() and returns a temporary filename or WP_Error. It does not install, validate, or delete the file for you.

require_once ABSPATH . 'wp-admin/includes/file.php';

$tmp = download_url( $url, 30 );
if ( is_wp_error( $tmp ) ) {
    return $tmp;
}

try {
    // Validate extension, real MIME, size, and product-specific content here.
    return myplugin_import_downloaded_file( $tmp );
} finally {
    if ( file_exists( $tmp ) ) {
        wp_delete_file( $tmp );
    }
}

Do not infer trust from a remote Content-Type or filename. Use the file upload security skill before sideloading into Media Library.

Test and observe

  • Use pre_http_request in tests to return deterministic fake responses.
  • Assert timeout, host, headers, body, error/status, malformed JSON, 429/5xx, oversized response, redirect, and cleanup behavior.
  • Inject syntactically valid but malicious control fields (sql, download_url, callback/path values) and assert they cannot reach an executable sink.
  • Use http_api_debug only for redacted diagnostics; never dump full requests.
  • Do not set sslverify => false, including in local examples. Fix CA/proxy configuration instead.

Critical rules

  • Use WP HTTP functions; never raw cURL or file_get_contents( $url ).
  • Use wp_safe_remote_* plus an allowlist for configurable destinations.
  • Bound timeout, redirects, and response size; handle WP_Error and status.
  • Keep TLS verification enabled and redact secrets/personal data.
  • Retry only when the operation is demonstrably idempotent.
  • Validate decoded response structure and downloaded file content.
  • Keep executable SQL, update trust, callbacks, and authorization policy local.

Cross-references

  • Use wp-security-deep for SSRF review.
  • Use wp-security-secrets and wp-connectors-api for credentials.
  • Use wp-file-upload-security for downloaded/sideloaded files.
  • Use wp-privacy-personal-data for external personal-data transfers.

Core references

  • wp-includes/http.php: safe wrappers and URL validation.
  • wp-includes/class-wp-http.php: request arguments and response contract.
  • wp-admin/includes/file.php: download_url() and temporary-file cleanup.

References

Keep looking

Skills are one crate of 328,083. Ordering is by how many stacks a row turns up in, so the top of any crate is what has actually been picked rather than what has the most stars.