Screenshots (PNG / JPEG)

The screenshot endpoints capture a publicly accessible webpage or raw HTML markup as a PNG or JPEG image, through the same asynchronous pipeline as the PDF endpoints: submit a job, poll its status, download the image. One screenshot costs one render unit plus its bytes — the same monthly quotas and the same per-file limits as PDFs apply (150 MB per file, 24-hour retention, 3 download attempts, 1 active stream). Available on every plan.

Endpoint: POST /url-to-image

POST https://api.pdfik.net/url-to-image

Request Body

ParameterTypeRequiredDescription
urlstringYesThe public URL of the page to capture. Local/internal IPs are blocked.
webhook_urlstringNoOptional callback URL where the job outcome payload will be POSTed. Credentials must not be embedded in the URL (https://user:pass@host is rejected) — configure delivery headers in the dashboard instead.
optionsobjectNoImage output options: format, full_page, quality, viewport. See Image Options.
renderobjectNoTimeout and wait selectors for browser load states (Pro+ plans only). See Browser Options.
authobjectNoAuthorization settings for pages requiring login credentials (Pro+ plans only). Same type/value contract as on /url-to-pdf: basic ('username:password') or bearer (token).
deliveryobjectNoUpload the finished image straight to your own bucket via a presigned PUT URL (Pro+ plans only). See Delivery to Your Own Bucket.
testbooleanNoRun the job in test mode: full pipeline, no real render, the bundled sample PNG is returned. Quotas are not debited. Defaults to false.

Endpoint: POST /html-to-image

POST https://api.pdfik.net/html-to-image

Request Body

ParameterTypeRequiredDescription
htmlstringYesThe raw HTML markup to capture. Sanitized with the same allowlist as /html-to-pdf: <script>, <style>, <iframe> and similar elements are removed together with their content; styling works via inline style="…" attributes. For full pages with stylesheets, host them and use /url-to-image.
webhook_urlstringNoOptional callback URL where the job outcome payload will be POSTed. Credentials must not be embedded in the URL (https://user:pass@host is rejected) — configure delivery headers in the dashboard instead.
optionsobjectNoImage output options: format, full_page, quality, viewport. See Image Options.
renderobjectNoTimeout and wait selectors for browser load states (Pro+ plans only). See Browser Options.
deliveryobjectNoUpload the finished image straight to your own bucket via a presigned PUT URL (Pro+ plans only). See Delivery to Your Own Bucket.
testbooleanNoRun the job in test mode: full pipeline, no real render, the bundled sample PNG is returned. Quotas are not debited. Defaults to false.

Image Options (options)

ParameterTypeDefaultDescription
formatstring"png"Output image format: "png" or "jpeg" ("jpg" is accepted as an alias for "jpeg").
full_pagebooleanfalseCapture the whole scrollable page instead of just the visible area. See Page height.
qualityinteger—JPEG quality, 1-100. JPEG only — sending quality with format: "png" is rejected with 422 before anything is charged.
viewportobject1024x768The browser window the page is opened in: {"width": …, "height": …} in CSS pixels, width 320-1920 and height 320-8192. Outside that range the request is rejected with 422. The viewport sets the image width; with full_page: false (the default) it also sets the image height.

You choose the size

There is no scaling, fitting or guessing anywhere in this endpoint. We open the page in exactly the window you ask for and capture it, so the output size is something you decide, not something you discover. With the defaults that is a flat 1024x768 image — a tablet-sized window, enough for a preview or a social card without asking our renderers for a full page every time.

If you want a desktop shot, ask for a desktop window: {"width": 1920, "height": 1080}. If you want a phone shot, ask for a phone: {"width": 390, "height": 844}. The page itself decides how it responds to that window, exactly as it would in a real browser.

Page height

With full_page: true the image height follows the real page, and is clipped at 8,192 px. That is a ceiling, not a target: a page that is 2,000 px long gives a 2,000 px image, not a padded 8,192 px one. A page longer than the ceiling is cut off there — the job still finishes done with the top of the page. Content that overflows horizontally, beyond the viewport width, is never captured.

For pages longer than the ceiling, capture them in parts with full_page: false, or render them with /url-to-pdf, which has no such limit.

The capture itself must finish within 60 seconds, independently of render.page_load_timeout_ms; a capture that takes longer fails the job with PAGE_TIMEOUT and is not retried. Use full_page: false or a smaller viewport for very long or heavy pages.

{
  "url": "https://example.com",
  "options": {
    "format": "jpeg",
    "quality": 80,
    "full_page": true,
    "viewport": { "width": 1920, "height": 1080 }
  }
}

Delivery to Your Own Bucket (delivery)

Add a delivery object (mode, url) to have the finished image uploaded straight to your own storage via a presigned PUT URL — nothing is stored on PDFik's side: once the job is done, the job.finished webhook reports the outcome only (no file_url, no expires_at — we never record where the file went), and the download endpoint answers 404. Pro and Business plans. The URL must use https on the standard port 443 (any other port is rejected with 422). The full rules (URL requirements, expiry, retries, error behavior) are documented in Delivery to Your Own Bucket; it cannot be combined with test mode.

Request Headers

HeaderRequiredDescription
X-API-KeyYesYour API key (sk_live_...).
Idempotency-KeyNo1-255 characters from A-Z a-z 0-9 _ - . :. Retrying a request with the same key returns the original job instead of creating (and billing) a duplicate. Recommended for retry-after-timeout logic.

The Asynchronous Flow

  1. Submit Job: Post the URL or HTML to /url-to-image or /html-to-image. You'll receive a job_id and a status of queued.
  2. Poll Status: Send GET requests to /jobs/{job_id} until status is done (or failed).
  3. Download File: Send a request to /jobs/{job_id}/download to stream the image binary directly. The response Content-Type is image/png or image/jpeg (matching the requested format), with the filename {job_id}.png or {job_id}.jpg. The file is available for 24 hours after generation (the exact moment is the expires_at field in the status response); at most 3 download attempts and 1 active stream are allowed per file. See File Retention & Downloads.

Test Mode

Test mode ("test": true) works on both screenshot endpoints: the job runs through the real pipeline without rendering, and the download returns the bundled sample PNG — for both requested formats (a jpeg test job is also served the PNG sample). Test jobs are free — they consume no PDF or data-volume quota — and are rate-limited separately. See Testing.


Integration Examples

1. Basic Request (curl)

curl -X POST https://api.pdfik.net/url-to-image \
  -H "X-API-Key: sk_live_YOUR_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "url": "https://example.com",
    "options": { "format": "png", "full_page": true }
  }'

Response (202 Accepted):

{
  "job_id": "8bfa5cc3-09ef-436f-80d4-a0eb867123aa",
  "status": "queued",
  "detail": "Job successfully sent to processing queue."
}

2. JavaScript (ES6 Fetch)

const API_KEY = 'sk_live_YOUR_KEY';

async function captureScreenshot(url) {
  // 1. Submit the URL to start the screenshot job
  const submitRes = await fetch('https://api.pdfik.net/url-to-image', {
    method: 'POST',
    headers: {
      'X-API-Key': API_KEY,
      'Content-Type': 'application/json'
    },
    body: JSON.stringify({
      url,
      options: { format: 'jpeg', quality: 80 }
    })
  });

  if (!submitRes.ok) throw new Error(`Failed to submit job: ${submitRes.statusText}`);
  const { job_id } = await submitRes.json();

  // 2. Poll the job status endpoint until it completes or fails
  let status = 'queued';
  while (status !== 'done' && status !== 'failed') {
    await new Promise(resolve => setTimeout(resolve, 2000));

    const statusRes = await fetch(`https://api.pdfik.net/jobs/${job_id}`, {
      headers: { 'X-API-Key': API_KEY }
    });

    if (statusRes.status === 429) continue; // rate limited: wait and poll again
    if (!statusRes.ok) throw new Error(`Status check failed: ${statusRes.status} ${await statusRes.text()}`);
    status = (await statusRes.json()).status;
  }

  if (status === 'failed') throw new Error('Screenshot job failed');

  // 3. Download the image (Content-Type: image/jpeg here)
  const downloadRes = await fetch(`https://api.pdfik.net/jobs/${job_id}/download`, {
    headers: { 'X-API-Key': API_KEY }
  });
  if (!downloadRes.ok) throw new Error(`Download failed: ${downloadRes.status} ${await downloadRes.text()}`);
  return await downloadRes.arrayBuffer(); // returns image binary buffer
}

3. Python (httpx)

import time
import httpx

API_KEY = "sk_live_YOUR_KEY"
BASE_URL = "https://api.pdfik.net"
HEADERS = {"X-API-Key": API_KEY}


def html_to_image(html: str, output_path: str = "output.png") -> None:
    with httpx.Client(base_url=BASE_URL, headers=HEADERS) as client:
        # 1. Submit
        submit = client.post("/html-to-image", json={
            "html": html,
            "options": {"format": "png", "viewport": {"width": 1280, "height": 720}},
        })
        submit.raise_for_status()
        job_id = submit.json()["job_id"]

        # 2. Poll
        status = "queued"
        while status not in ("done", "failed"):
            time.sleep(2)
            r = client.get(f"/jobs/{job_id}")
            if r.status_code == 429:  # rate limited: wait and poll again
                continue
            r.raise_for_status()
            status = r.json()["status"]

        if status == "failed":
            raise RuntimeError("Screenshot job failed")

        # 3. Download
        with open(output_path, "wb") as f:
            with client.stream("GET", f"/jobs/{job_id}/download") as response:
                response.raise_for_status()
                for chunk in response.iter_bytes():
                    f.write(chunk)


html_to_image('<h1 style="color: #1e3a8a;">Hello</h1>')

SDKs and CLI

The official clients cover both endpoints: urlToImage / htmlToImage in the JavaScript and Java SDKs, url_to_image / html_to_image in the Python SDK, and pdfik url-to-image <url> / pdfik html-to-image <file.html | -> in the CLI.