ScreenshotNeo

BlogHow-to

How to Use the ShrinkTheWeb API in a Laravel Website

Learn the safe Laravel HTTP Client pattern for ShrinkTheWeb, what its older setup guide documents, and what to verify before sending a live request.

By the ScreenshotNeo team4 October 202610 min read

Short answer: Laravel’s HTTP Client can send a server-side request to ShrinkTheWeb and inspect its response. However, the available ShrinkTheWeb setup guide is historical Drupal documentation last updated in 2019; it does not verify the service’s current endpoint, authentication format, response type, or parameter contract. Treat the code below as an integration pattern until you confirm those details in current ShrinkTheWeb documentation or your account.

This distinction matters: a Laravel request can be written correctly while still failing because it uses outdated provider details. Keep credentials on your server, use a finite timeout, validate submitted URLs, and check the response status and content type before storing or serving the result.

1. What the available ShrinkTheWeb guide documents

The published Drupal integration guide says users could obtain an Access key and Secret key from their account profile. It describes options for caching, custom screenshot size, specific-page capture, delay, and quality. It lists parameter names including url, custom_width, full_length, max_height, native_resolution, widescreen_resolution_y, delay, and quality. The guide describes delay in seconds after page load and quality from 1 to 100. It also says capturing a page other than a site’s homepage required the “Inside Pages” upgrade at that time. These are historical details, not confirmation of today’s API or plan entitlements. Read the Drupal.org setup guide.

The Drupal project page marks its module unsupported and obsolete, and says it appeared no longer supported as of January 31, 2022. That status concerns the Drupal module; it does not establish the present status of the ShrinkTheWeb service. For a new Laravel integration, use Laravel’s general-purpose HTTP Client and verify the provider contract separately. Drupal.org project status.

2. Confirm the current API contract first

Before writing a production request, confirm these items in current ShrinkTheWeb documentation or your account:

  • The API endpoint and HTTP method.
  • Whether the Access key, Secret key, or both are required, and whether they belong in query parameters, headers, or a signed request.
  • The current names and allowed values for URL, dimensions, full-page capture, delay, and quality options.
  • Whether the response contains image bytes, JSON with a hosted image URL, or another result format.
  • How errors are represented, including rate limits and account or plan restrictions.
  • Whether inside-page capture and other options are available on your current plan.

The code in the next section deliberately uses a configurable endpoint and labels the request fields as placeholders. The available evidence does not verify a current ShrinkTheWeb endpoint, credential transmission scheme, response format, or error schema. Do not copy guessed values into production.

3. Configure Laravel securely

Put provider credentials in the server environment and expose them through Laravel configuration. Never put secrets in Blade templates, browser JavaScript, public image URLs, source control, or logs.

# .env — use the actual values and names confirmed for your account
SHRINKTHEWEB_ENDPOINT=https://replace-with-current-endpoint
SHRINKTHEWEB_ACCESS_KEY=replace-me
SHRINKTHEWEB_SECRET_KEY=replace-me

Add a configuration entry in config/services.php:

'shrinktheweb' => [
    'endpoint' => env('SHRINKTHEWEB_ENDPOINT'),
    'access_key' => env('SHRINKTHEWEB_ACCESS_KEY'),
    'secret_key' => env('SHRINKTHEWEB_SECRET_KEY'),
],

After changing environment-backed configuration in a deployed application, follow your deployment’s normal Laravel configuration-cache process. Do not commit the actual .env values.

4. Make a server-side request with Laravel

Laravel’s HTTP Client is a framework-supported wrapper around Guzzle. It accepts query parameters as the second argument to get, supports headers, and provides response methods such as body(), status(), successful(), and failed(). The following example shows the request shape, finite timeouts, and response checks. Replace the endpoint, authentication, and placeholder parameters with the current provider contract.

<?php

namespace App\Services;

use Illuminate\Support\Facades\Http;
use RuntimeException;

final class ShrinkTheWebClient
{
    /**
     * Integration pattern only. Confirm endpoint, authentication,
     * parameter names, and response type against current provider docs.
     */
    public function capture(string $targetUrl): string
    {
        $endpoint = config('services.shrinktheweb.endpoint');
        $accessKey = config('services.shrinktheweb.access_key');

        if (! is_string($endpoint) || $endpoint === '' || ! is_string($accessKey) || $accessKey === '') {
            throw new RuntimeException('ShrinkTheWeb is not configured.');
        }

        $validatedUrl = filter_var($targetUrl, FILTER_VALIDATE_URL);
        $scheme = parse_url($targetUrl, PHP_URL_SCHEME);
        if (! $validatedUrl || ! in_array(strtolower((string) $scheme), ['http', 'https'], true)) {
            throw new RuntimeException('A valid HTTP or HTTPS target URL is required.');
        }

        // These field names are placeholders until verified for your account.
        $response = Http::connectTimeout(5)
            ->timeout(30)
            ->get($endpoint, [
                'url' => $targetUrl,
                'access_key' => $accessKey,
            ]);

        if ($response->failed()) {
            // Log a safe status only. Do not log credentials or a URL containing secrets.
            throw new RuntimeException('ShrinkTheWeb request failed with HTTP status '.$response->status().'.');
        }

        $contentType = strtolower((string) $response->header('Content-Type'));
        if (! str_starts_with($contentType, 'image/')) {
            // A provider may instead return JSON or a hosted URL; handle that only
            // after confirming its current response contract.
            throw new RuntimeException('ShrinkTheWeb returned a non-image response; verify the response contract.');
        }

        return $response->body();
    }
}

Use it from a controller or queued job, then store the returned bytes in private or appropriately controlled storage. Avoid returning arbitrary provider responses directly to a browser without validating their type and intended use.

Laravel’s HTTP Client documentation describes query parameters, headers, timeouts, and response inspection. Choose the documentation version matching your application; the reviewed Laravel 12 page points readers to the newer version. Laravel HTTP Client documentation.

5. Validate user-submitted URLs and control access

A screenshot endpoint that accepts arbitrary URLs can become a server-side request forgery (SSRF) path. Basic URL syntax validation is not enough if your application must prevent requests to internal services or private network addresses.

  • Accept only http and https schemes.
  • Apply an allowlist of domains if the feature is intended for a known set of sites.
  • If arbitrary public URLs are required, resolve hostnames and block loopback, private, link-local, and other internal address ranges; account for redirects and DNS rebinding in the enforcement design.
  • Set limits on URL length, request frequency, concurrent jobs, and stored output size.
  • Keep provider credentials server-side, and redact them from application logs and exception reports.

The sample’s syntax check is only a starting point; it is not a complete SSRF defense.

6. Handle response formats and store captures

Do not assume that a successful HTTP status means the response is an image. Check the content type and, where appropriate, verify the file signature or decode the image before accepting it. If the current API returns JSON containing a hosted URL instead of image bytes, parse and validate that documented response and decide whether to store the URL or fetch the image separately.

For image bytes, choose a storage disk and path that match your privacy and retention requirements. Generate a stable cache key from the normalized target URL and capture options. Store metadata such as capture time, requested dimensions, content type, and provider status without storing secrets. Define a refresh policy: screenshots can become stale as the target page changes.

7. Options, page selection, and screenshot size

The historical Drupal guide identifies these option names, but they must be checked against the current API before use:

Historical option Documented purpose What to verify
url Target page address Current parameter name, accepted URL forms, and redirect behavior
custom_width Custom capture width Units, valid range, and interaction with other dimensions
full_length Full-length capture option Accepted values and maximum page height
max_height Maximum height Units and truncation behavior
native_resolution Native-resolution setting Current meaning and supported values
widescreen_resolution_y Widescreen resolution height Whether still supported and how width is selected
delay Wait after page load, in seconds Bounds, default, and whether this waits for dynamic content
quality Image quality from 1 to 100 Formats to which it applies and current accepted range

For an inside page, submit that page’s full URL if the current contract supports it. The old guide says non-homepage captures required the “Inside Pages” upgrade, so confirm current access before building a feature around it. If a capture shows the wrong page, check the requested URL, redirects, access restrictions, and the service’s documented page-selection behavior.

8. cURL, Python, and Node.js request shapes

These examples demonstrate common HTTP mechanics only. The endpoint, credential placement, and parameter names below are placeholders, and the handling assumes image bytes only after the provider’s current response contract confirms that behavior.

cURL

curl --fail-with-body --get "$SHRINKTHEWEB_ENDPOINT" \
  --data-urlencode "url=https://example.com/page" \
  --data-urlencode "access_key=$SHRINKTHEWEB_ACCESS_KEY" \
  --output capture.bin

Python

import os
import requests

endpoint = os.environ["SHRINKTHEWEB_ENDPOINT"]  # Confirm current endpoint first.
params = {
    "url": "https://example.com/page",
    "access_key": os.environ["SHRINKTHEWEB_ACCESS_KEY"],
}

response = requests.get(endpoint, params=params, timeout=(5, 30))
response.raise_for_status()
content_type = response.headers.get("Content-Type", "").lower()
if not content_type.startswith("image/"):
    raise RuntimeError(f"Expected image bytes; received {content_type or 'unknown content type'}")

with open("capture.bin", "wb") as output:
    output.write(response.content)

Node.js

const endpoint = process.env.SHRINKTHEWEB_ENDPOINT; // Confirm current endpoint first.
if (!endpoint) throw new Error('Set SHRINKTHEWEB_ENDPOINT');

const requestUrl = new URL(endpoint);
requestUrl.searchParams.set('url', 'https://example.com/page');
requestUrl.searchParams.set('access_key', process.env.SHRINKTHEWEB_ACCESS_KEY ?? '');

const response = await fetch(requestUrl, { signal: AbortSignal.timeout(30_000) });
if (!response.ok) throw new Error(`Request failed with HTTP ${response.status}`);
const contentType = response.headers.get('content-type') ?? '';
if (!contentType.toLowerCase().startsWith('image/')) {
  throw new Error(`Expected image bytes; received ${contentType || 'unknown content type'}`);
}
const bytes = new Uint8Array(await response.arrayBuffer());
await import('node:fs/promises').then(({ writeFile }) => writeFile('capture.bin', bytes));

In production, use the exact authentication and response handling documented for your account. Do not put a secret in a command that will be saved in shell history or exposed in process listings when safer secret injection is available.

9. Troubleshooting

Symptom Likely cause What to do
Authentication failure Wrong key, missing secret, or outdated assumption about where credentials go Check current account credentials and documented signing or header/query requirements. Redact secrets while debugging.
Not found or endpoint error Endpoint copied from an old integration or placeholder not replaced Confirm the current API base URL from provider documentation or account materials.
HTTP success but file is not an image The API may return JSON, an error document, or a hosted URL Inspect status and content type safely; implement the current documented response schema.
Homepage appears instead of the requested page Inside-page access may be restricted, or the target URL was normalized or redirected Check the final requested URL and current plan entitlement; the historical guide described an “Inside Pages” upgrade.
Screenshot is blank or incomplete The page may require authentication, client-side rendering, or more time to load Check provider options for delay or page readiness, and confirm the target is publicly reachable by the service.
Timeouts Slow target site, slow rendering, or network delay Use finite but suitable connect and total timeouts, queue long work, and provide a retry policy for transient failures.
Unexpectedly large response Full-page or high-resolution capture Set documented size limits, validate output dimensions, and control storage and response size.
Laravel changes do not appear Configuration cache still contains old values Refresh Laravel’s configuration cache using the deployment procedure for your application.

10. Performance, reliability, and cost

Screenshot generation depends on both your request and the target website’s load time. Avoid making visitors wait on a synchronous capture when the target can be slow: dispatch a queued job, store a pending state, and return a job or page status to the browser. Retry only transient network or server errors, with a small bounded retry count and backoff. Do not repeatedly retry authentication, validation, or plan errors.

Cache by normalized URL plus every option that affects the output, such as dimensions, full-page mode, quality, and delay. Set a refresh lifetime based on how often the source pages change and the provider’s terms. Add per-user rate limits and deduplicate identical in-flight captures to reduce duplicate work.

The available research does not establish current ShrinkTheWeb pricing, quotas, account availability, or the cost of individual capture options. Check the current account terms before estimating operating cost. Monitor request counts, response sizes, latency, status codes, and cache hit rate without logging credentials or sensitive URLs.

11. Or skip the browser setup

ScreenshotNeo is a website screenshot API and MCP server from Yorker Media. A single GET request can return a PNG, JPEG, WebP, or PDF. For a Laravel server-side call, use Laravel’s HTTP Client and keep the API key in environment-backed configuration:

use Illuminate\Support\Facades\Http;

$response = Http::timeout(90)->get('https://api.screenshotneo.com/v1/shot', [
    'access_key' => config('services.screenshotneo.access_key'),
    'url' => 'https://stripe.com',
]);

if (! $response->successful()) {
    throw new RuntimeException('ScreenshotNeo request failed with HTTP '.$response->status());
}

$imageBytes = $response->body();

See the ScreenshotNeo API documentation for request options. Cookie banners, newsletter popups, and chat widgets are removed before capture; each cleanup step can be turned off. Bot checks, blank pages, timeouts, failed loads, and cache hits are not billed, and response headers report the page verdict and billing status. Its MCP server lets AI agents use screenshot, page-info, and PDF-capture tools. The free plan includes 1,000 screenshots a month with no card; paid plans start at $5 for 3,000 screenshots.

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

12. FAQ

Can I use the old Drupal module in a Laravel app?

No. It is a Drupal integration, and Drupal.org marks the project unsupported and obsolete. Use Laravel’s HTTP Client for the outbound request.

Can I safely copy the historical parameter list into my request?

Not without checking current ShrinkTheWeb documentation. The names and behavior come from a Drupal guide last updated in 2019.

Should a browser call the screenshot API directly?

Keep provider credentials on the server. Have your Laravel application make the request, then return only the result your users are allowed to access.

Does a successful request mean the screenshot was billed?

The available ShrinkTheWeb research does not establish its billing behavior. Check current provider terms and account usage details.

Sources