agentsclimarketplace

Wp http api client

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

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.From its SKILL.md

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.

SKILL.md

10.4 KB, ~2.2k tokens by cl100k_base, 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

What ships with it: 1 file

224 B alongside SKILL.md

agents/

Keep looking

Skills are one crate of 325,949. 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.