ScreenshotNeo

BlogHow-to

HTML/CSS to Image Screenshot API Tutorial for WordPress Sites in India

Call HTML/CSS to Image from WordPress with server-side PHP, protect your API key, handle the image URL, and check pricing and billing details for India.

By the ScreenshotNeo team4 October 202612 min read

Direct answer: call HTML/CSS to Image (HCTI) from WordPress PHP with wp_remote_post(). Send either HTML (and optional CSS) or a fully qualified public page URL to https://hcti.io/v1/image, authenticate with HTTP Basic using your API ID as the username and API key as the password, then check the HTTP status and JSON response before displaying the returned image URL. Keep credentials on the server; do not make the API call from browser JavaScript.

This tutorial is for WordPress site owners and PHP developers in India. The implementation is the same regardless of where the site owner is located. HCTI’s cited prices are published in USD; the sources reviewed do not establish India-specific taxes, billing currency, data residency, or support terms.

1. Choose HTML or a page URL

HCTI accepts one of two rendering inputs: html, for markup your WordPress application constructs, or url, for a public webpage. You may provide CSS with either input. Do not send both html and url; the API documentation says the URL takes precedence when both are present. The create-image endpoint accepts JSON or form data. This guide uses JSON. See the HCTI API guide for authentication and parameter details.

  • Use HTML and CSS for a generated card, report, social graphic, or other markup your application controls.
  • Use a URL to capture an existing public page. The page must be reachable by HCTI’s renderer.
  • Use an authorized header or short-lived session token only if you are authorized to access a protected page and the API supports the required header. HCTI does not automate an interactive login flow. Do not use this technique to access pages without permission.

For a WordPress site, decide whether a screenshot should be generated by an administrator action, a scheduled job, or a user action. Avoid rendering on every page view unless that behavior is intentional: repeated visitors can cause unnecessary API calls, latency, and usage.

2. Configure and protect the credentials

  1. Get the API ID and API key from your HCTI dashboard.
  2. Create or select a key with only the permissions the integration needs. HCTI documents images:create for creating an image and advises treating the key as a password. See HCTI’s API key guidance.
  3. Store both values in server-side environment configuration or a secrets manager. Do not place the key in a theme template, public shortcode attribute, JavaScript bundle, HTML, or a URL.
  4. Make sure production logs and error pages do not print the Authorization header, secret, or unreviewed upstream response body.

For example, configure HCTI_API_ID and HCTI_API_KEY in the PHP process environment, then read them with getenv(). The exact mechanism for setting environment variables depends on your host. Do not commit real credentials to a theme or plugin repository.

3. Make the request from WordPress PHP

The following is a self-contained function for a plugin or a site-specific plugin. It sends either a public URL or HTML, optionally with CSS, handles WordPress transport errors, checks the HTTP response, and validates the returned image URL before returning it. It is an illustrative integration; adapt it to your application’s input, permission checks, and error reporting.

<?php
/**
 * Create an image with HTML/CSS to Image and return its image URL.
 *
 * Pass exactly one of $page_url or $html. CSS is optional.
 * Returns WP_Error on configuration, transport, HTTP, or response errors.
 */
function mysite_hcti_create_image( $page_url = '', $html = '', $css = '' ) {
    $api_id  = getenv( 'HCTI_API_ID' );
    $api_key = getenv( 'HCTI_API_KEY' );

    if ( ! $api_id || ! $api_key ) {
        return new WP_Error( 'hcti_missing_credentials', 'Screenshot service credentials are not configured.' );
    }

    // The API accepts either url or html, not both.
    $has_url  = is_string( $page_url ) && '' !== trim( $page_url );
    $has_html = is_string( $html ) && '' !== trim( $html );
    if ( $has_url === $has_html ) {
        return new WP_Error( 'hcti_input_required', 'Provide exactly one of a page URL or HTML.' );
    }

    $payload = array();
    if ( $has_url ) {
        // In production, constrain this to expected public hosts if caller-controlled.
        $payload['url'] = esc_url_raw( $page_url );
        if ( ! wp_http_validate_url( $payload['url'] ) ) {
            return new WP_Error( 'hcti_invalid_url', 'Provide a valid public page URL.' );
        }
    } else {
        $payload['html'] = $html;
    }
    if ( '' !== $css ) {
        $payload['css'] = $css;
    }

    $response = wp_remote_post(
        'https://hcti.io/v1/image',
        array(
            'headers' => array(
                'Authorization' => 'Basic ' . base64_encode( $api_id . ':' . $api_key ),
                'Content-Type'  => 'application/json',
                'Accept'        => 'application/json',
            ),
            'body'        => wp_json_encode( $payload ),
            'timeout'     => 30,
            'redirection' => 2,
        )
    );

    if ( is_wp_error( $response ) ) {
        // Log a sanitized error if needed; never log credentials.
        return new WP_Error( 'hcti_transport_error', 'The screenshot request could not reach the service.' );
    }

    $status = wp_remote_retrieve_response_code( $response );
    $body   = wp_remote_retrieve_body( $response );
    if ( $status < 200 || $status >= 300 ) {
        // Keep the upstream body out of public-facing errors.
        return new WP_Error( 'hcti_http_error', 'The screenshot service returned HTTP ' . absint( $status ) . '.' );
    }

    $data = json_decode( $body, true );
    if ( ! is_array( $data ) || empty( $data['url'] ) || ! is_string( $data['url'] ) ) {
        return new WP_Error( 'hcti_bad_response', 'The screenshot service response did not contain an image URL.' );
    }

    $image_url = esc_url_raw( $data['url'] );
    if ( ! wp_http_validate_url( $image_url ) || 0 !== strpos( $image_url, 'https://' ) ) {
        return new WP_Error( 'hcti_bad_image_url', 'The screenshot service returned an invalid image URL.' );
    }

    return $image_url;
}

// Example: render a known public page. Do not pass an arbitrary visitor URL here.
$image_url = mysite_hcti_create_image( 'https://example.com/', '', '' );
if ( ! is_wp_error( $image_url ) ) {
    echo '<img src="' . esc_url( $image_url ) . '" alt="Page screenshot" loading="lazy">';
}

WordPress’s HTTP API reference documents wp_remote_post() and its WP_Error return path. The response helpers used above expose the status and body. HCTI’s examples show a JSON response with a url field; see its cURL example and response. Check the current response schema and API reference when deploying, because vendor behavior can change.

Security note for URL inputs: validating that a string is syntactically a URL is not enough if a visitor can choose the destination. Restrict user-controlled targets to an allowlist of domains you own or explicitly trust, and use WordPress’s safe HTTP request functions as appropriate. This prevents your site from becoming a proxy for requests to internal or unintended hosts. WordPress specifically advises using wp_safe_remote_post() when the request URL itself is user-controlled.

4. Request the image with cURL, Python, or Node.js

These examples demonstrate the same HCTI request outside WordPress. In each case, keep credentials in environment variables or a server-side secret store.

cURL

curl -X POST 'https://hcti.io/v1/image' \
  -u "$HCTI_API_ID:$HCTI_API_KEY" \
  -H 'Content-Type: application/json' \
  -H 'Accept: application/json' \
  --data '{"url":"https://example.com/"}'

Python

import os
import requests

api_id = os.environ["HCTI_API_ID"]
api_key = os.environ["HCTI_API_KEY"]

response = requests.post(
    "https://hcti.io/v1/image",
    auth=(api_id, api_key),
    json={"url": "https://example.com/"},
    timeout=30,
)
response.raise_for_status()
data = response.json()
image_url = data.get("url")
if not isinstance(image_url, str) or not image_url.startswith("https://"):
    raise ValueError("API response did not contain a valid image URL")
print(image_url)

Node.js

const apiId = process.env.HCTI_API_ID;
const apiKey = process.env.HCTI_API_KEY;
if (!apiId || !apiKey) throw new Error('Set HCTI_API_ID and HCTI_API_KEY');

const auth = Buffer.from(`${apiId}:${apiKey}`).toString('base64');
const response = await fetch('https://hcti.io/v1/image', {
  method: 'POST',
  headers: {
    Authorization: `Basic ${auth}`,
    'Content-Type': 'application/json',
    Accept: 'application/json',
  },
  body: JSON.stringify({ url: 'https://example.com/' }),
  signal: AbortSignal.timeout(30000),
});
if (!response.ok) throw new Error(`HCTI returned HTTP ${response.status}`);
const data = await response.json();
if (typeof data.url !== 'string' || !data.url.startsWith('https://')) {
  throw new Error('HCTI response did not contain a valid image URL');
}
console.log(data.url);

5. Display the result or import it into WordPress

The API response contains a URL for the generated image. For a page that only needs to show the result, escape that URL and use it as an image source. The HCTI documentation says the URL remains available while the account is active and describes delivery through Cloudflare caching and optimization; that availability is subject to account status and service terms. Do not treat the URL as an unconditional permanent archive.

If the image must live in the WordPress Media Library, that is a separate workflow: download the returned file server-side, validate the response and expected image type/size, then create a Media Library attachment. WordPress exposes media endpoints such as /wp/v2/media, but the exact upload procedure depends on whether you use PHP media functions or the REST API. Keep this step separate from image generation so retries do not create duplicate attachments.

HCTI documents PNG, JPG, WebP, and PDF formats. Its format parameter documentation says the format selects the extension in the returned URL; when omitted, the default URL serves PNG. Choose a format based on the destination: WebP can reduce transfer size where supported, PNG preserves transparency, and PDF is for document output rather than an <img> element.

6. Relevant options and workflow choices

Choice When to use it What to check
html plus optional css Generated cards, custom graphics, or markup assembled by WordPress Inline or otherwise reachable assets; valid markup; avoid embedding secrets in rendered HTML
url A public WordPress page or other public webpage The renderer can reach it without an interactive login; use a fixed or allowlisted destination
format Choose a URL extension such as PNG, JPG, WebP, or PDF Check current supported format values and whether the consuming client supports the output
Custom headers An authorized page that needs a supported request header Do not assume this performs browser login or bypasses access controls
Direct remote display Show the generated result without managing a local copy URL availability follows account status and service terms
Media Library import Need WordPress-managed media, local backups, or attachment metadata Download, validate, store, and deduplicate as a separate operation

The API supports additional parameters beyond this tutorial. Check the live HCTI documentation for options and accepted values rather than assuming an option name or default.

7. Cost and India-specific checks

HCTI’s pricing page listed the following vendor-published USD prices when checked on 2026-10-03. Plan names, quotas, prices, and features can change, so confirm the live page before choosing a plan.

Plan Published price/allowance Notes
Free 50 images/month No credit card listed; dynamic Open Graph images are not included in Free, according to the pricing page
Basic $14/month Check current limits and feature set
Pro $149/month Check current limits and feature set
Scale $749/month Check current limits and feature set

These are USD figures, not an estimate of what an India-based customer will pay after taxes or currency conversion. The reviewed sources do not establish GST/VAT treatment, INR billing, local data residency, or India-specific support. Confirm payment, tax, and data-processing terms directly with the vendor before deployment. See the HCTI pricing page.

For a WordPress site, estimate usage from actual generation events, not page views: count how many distinct screenshots your workflow creates, how often content changes, and whether you regenerate the same image. Cache or reuse results where appropriate. HCTI documents caching and request deduplication behavior; check its current API documentation for the exact parameters and plan conditions.

8. Troubleshooting

Symptom Likely cause Fix
WordPress returns WP_Error DNS, TLS, outbound HTTP, hosting firewall, or timeout problem Check the sanitized WordPress error, confirm outbound HTTPS is allowed, and retry with a bounded timeout. Do not disable TLS verification.
HTTP 401 or 403 Wrong API ID/key, malformed Basic Auth, or key lacks image-creation permission Verify the dashboard values, rotate any exposed key, and check that the key permits images:create.
HTTP 400 Missing input, both url and html sent, malformed JSON, or invalid parameter Send exactly one rendering input, ensure JSON encoding succeeds, and compare fields against the current API reference.
Successful status but no image URL Response shape changed or body is not valid JSON Inspect a sanitized response in a protected development log, confirm the live schema, and validate the url field before displaying it.
Blank or incomplete screenshot Target page failed to load resources, requires interactive login, or renders content after delayed scripts Test that the page is publicly reachable and assets load for an external renderer. For authenticated content, use only documented, authorized headers; do not expect interactive login automation.
Image is stale or regenerated too often Workflow does not reuse results or invalidates its cache too aggressively Store the generated URL with the source content/version and regenerate only when that input changes. Check current vendor caching and deduplication behavior.
Image URL stops working later Account is inactive or URL availability is subject to service terms Review the vendor’s current terms. If you need WordPress-managed retention, download and import the asset into your Media Library.
Request causes unexpected server-side access A visitor can supply an arbitrary target URL Do not proxy arbitrary destinations. Validate and allowlist permitted public hosts; use safe WordPress request handling for caller-controlled URLs.
Image appears broken in the browser Bad URL, unsupported format, or response not an image Open the returned URL during development, confirm the chosen format and response content type, and escape the final URL with esc_url().

9. Performance and reliability

  • Keep rendering out of the critical page path where possible. A remote render adds network and browser-rendering time. For noninteractive pages, queue work or generate after content changes, then show the latest saved result.
  • Use a finite timeout and explicit failure state. The example uses 30 seconds as an application limit, not a claim about HCTI’s render-time guarantee. Choose a timeout that fits your hosting limits and user experience.
  • Retry selectively. A transient transport failure may be retryable; invalid credentials and malformed input are not. Limit attempts and avoid retry loops that multiply usage.
  • Deduplicate work. Use a cache key derived from the URL or a content version plus relevant rendering inputs. Regenerate only when those inputs change.
  • Separate generation from storage. First obtain and validate the image URL. If importing it into WordPress, handle download and attachment creation as a second step with its own error handling.
  • Monitor usage and errors without logging secrets. Track status classes, generation counts, and failures. Avoid retaining rendered private data longer than your site needs.

10. Or skip the browser setup

If you want a screenshot API without setting up and maintaining your own browser rendering flow, ScreenshotNeo provides a one-request screenshot API and an MCP server for AI agents. It can return PNG, JPEG, WebP, or PDF. Cookie banners, popups, and chat widgets are removed before the shot; bot checks, blank pages, and failed loads are never billed. Its MCP server lets AI agents take screenshots. The free plan includes 1,000 screenshots a month with no card, and paid plans start at $5 for 3,000. All features are on every plan. See the ScreenshotNeo API documentation.

cURL

curl -G "https://api.screenshotneo.com/v1/shot" -d access_key=YOUR_API_KEY --data-urlencode url=https://stripe.com -o shot.webp

Python

import requests
r = requests.get("https://api.screenshotneo.com/v1/shot", params={"access_key": "YOUR_API_KEY", "url": "https://stripe.com"}, timeout=90)
open("shot.webp", "wb").write(r.content)

Node.js

const q = new URLSearchParams({ access_key: 'YOUR_API_KEY', url: 'https://stripe.com' });
const res = await fetch(`https://api.screenshotneo.com/v1/shot?${q}`);

Sign up free for 1,000 screenshots a month with no card.

11. Frequently asked questions

Can WordPress call HCTI from a shortcode?

Yes, but the shortcode should invoke server-side PHP and must not accept or expose the API key. Consider generating on an explicit action or when content changes rather than on every render of the shortcode.

Can HCTI capture a page behind a WordPress login?

It does not automate an interactive login flow. The documentation describes authorized headers or short-lived session credentials for permitted cases. Confirm current support and protect those credentials carefully.

Does the returned image URL last forever?

No unconditional guarantee should be assumed. HCTI describes availability while the account is active, subject to its terms. Import the file into your own media storage if you need a separately managed copy.

Are HCTI prices in INR for Indian customers?

The cited pricing page shows USD prices. Confirm the current checkout currency, applicable taxes, and billing terms with HCTI; the reviewed sources do not settle India-specific billing details.

Can I use the API to generate a PDF?

HCTI documents PDF as an output format. Check its current format and PDF-specific options before relying on page sizes or document layout behavior.

Sources