---
title: "Getting Started"
description: "Authenticate, choose an endpoint and make your first request to the html2img API."
url: "https://html2img.com/docs/getting-started/"
---

# Getting Started

Welcome to html2img. This guide covers the four endpoints, authentication, and the response format you can expect. The HTML and Screenshot endpoints can also return a PDF instead of a PNG via the [format parameter](https://html2img.com/docs/parameters/format/).

> **Note: Machine-readable specification**
>
> Every endpoint, parameter, response shape and error code on this page is also published as an OpenAPI 3.0 document at [/openapi.json](https://html2img.com/openapi.json), and as YAML at [/openapi.yaml](https://html2img.com/openapi.yaml). Point a client generator, an API explorer or an AI agent at it rather than transcribing this page by hand.

## Authentication

All API requests require authentication using an API key. Include your key in the `X-API-Key` header on every request.

[Read the authentication guide →](https://html2img.com/docs/authentication/)

## Skip the raw HTTP calls with an official SDK

The API works from any language that can make an HTTP request, but most stacks have an official, maintained client that handles authentication, validation and error handling for you:

- **[PHP](https://html2img.com/integrations/php/)** - a typed client for any modern PHP app. `composer require html2img/html2img-php`
- **[Laravel](https://html2img.com/integrations/laravel/)** - a facade, config file and storage helpers, built on the PHP SDK. `composer require html2img/html2img-laravel`
- **[JavaScript](https://html2img.com/integrations/javascript/)** - a zero-dependency TypeScript-ready client for Node.js, Bun, Deno and edge runtimes. `npm install @html2img/client`
- **[Python](https://html2img.com/integrations/python/)** - a zero-dependency client with sync and async APIs. `pip install html2img-client`
- **[Ruby and Rails](https://html2img.com/integrations/ruby/)** - a zero-dependency gem with a Railtie. `bundle add html2img-client`

Working in another stack? [Integrations](https://html2img.com/integrations/) also covers Django, WordPress, Statamic, Craft CMS and GitHub Actions, and everything below applies to every language.

## Which should I use?

Pick raw HTML for full control, the URL endpoint for screenshots of pages you already host, or named templates when you want to skip the markup step entirely.

## Four endpoints

Three render endpoints that each consume one credit per image, and a free account status endpoint.

### 1. HTML and CSS API
```
POST https://app.html2img.com/api/html
```
Send raw HTML and CSS, get back a PNG. Run inline JavaScript up to a 30 second budget. Best for full design control.

### 2. Screenshot API
```
POST https://app.html2img.com/api/screenshot
```
Send a public URL, get back a PNG of the rendered page. Best for capturing pages you already host.

### 3. Templates API
```
POST https://app.html2img.com/api/v1/templates/[slug]
```
Send a JSON payload to a [named template](https://html2img.com/templates/) endpoint. Best for skipping the markup step.

### 4. Account Status API
```
GET https://app.html2img.com/api/me
```
Returns the account behind your API key: plan, credit balance and renewal date. It never consumes a credit and works even when your account is out of credits, so use it to verify a key or to check your balance before a batch. See the [account status docs](https://html2img.com/docs/account/) for the full response reference.

> **Important**
>
> For the Screenshot API, use the `webhook_url` parameter when render time is unpredictable. The 30 second sync timeout is enough for most requests, but a slow third-party page can blow past it. See [webhook_url docs](https://html2img.com/docs/parameters/webhook-url/).
>
> For best results:
> - Use `webhook_url` for slow Screenshot API requests
> - Keep `dpi` at 1 unless you specifically need retina output
> - Use sync requests for the HTML API or fast Screenshot requests

## Required Parameters

### HTML and CSS API

| Parameter | Type | Description |
|-----------|------|-------------|
| `html` | string | The HTML content to render. Can include inline CSS and JavaScript. |

### Screenshot API

| Parameter | Type | Description |
|-----------|------|-------------|
| `url` | string | The URL to capture. Must be a valid, publicly accessible URL. |

## Optional Parameters

Both APIs support the following optional parameters:

| Parameter | Type | Default | Description |
|-----------|------|---------|-------------|
| `css` | string | null | Additional CSS to inject into the page |
| `width` | integer | 1440 | Viewport width (1-5000 pixels) |
| `height` | integer | 900 | Viewport height (1-5000 pixels) |
| `fullpage` | boolean | false | Whether to capture the full page height |
| `dpi` | integer | 1 (Screenshot), 2 (HTML) | Device pixel ratio (1-4). Fullpage captures always render at 1. |
| `webhook_url` | string | null | URL to receive a JSON callback with the image URL when the render finishes |
| `wait_for_selector` | string | null | Wait for a specific element to appear before capturing (does not work with iframes) |
| `ms_delay` | integer | null | Fixed delay in milliseconds before capture, 1-5000 (useful for iframes and embedded content) |

The Screenshot API also supports:

| Parameter | Type | Default | Description |
|-----------|------|---------|-------------|
| `selector` | string | null | CSS selector to capture a specific element |

> **Warning**
>
> Higher DPI values (2-4) significantly increase processing time and memory usage. We allow these values to support specific use cases, but they often lead to timeouts when not used with `webhook_url`. For most cases, a DPI of 1 provides enough quality.

## Response Format

### Successful Response
```json
{
    "success": true,
    "id": "8a9dda43-5f42-4b93-8ff4-cd69ed32d402",
    "expires_at": null,
    "credits_remaining": 950,
    "url": "https://i.html2img.com/image-1786092598870-921691.png"
}
```

`expires_at` is `null` on paid plans, where renders stay hosted permanently. On the free tier it carries an ISO 8601 timestamp - free renders are hosted for 7 days, and upgrading makes every render still hosted permanent.

For when using `webhook_url`:
```json
{
    "success": true,
    "id": "8a9dda43-5f42-4b93-8ff4-cd69ed32d402",
    "expires_at": null,
    "credits_remaining": 950,
    "status": "processing",
    "message": "Screenshot generation started",
    "url": "https://i.html2img.com/image-1786092598870-921691.png"
}
```

The `url` in a processing response is where the image will land - it is not live until the render finishes and your webhook fires.

The webhook payload, posted to your `webhook_url` when the render finishes, has its own shape. The render is identified by `log_id`, which matches the `id` from your initial response:
```json
{
    "status": "success",
    "message": "Screenshot generated successfully",
    "url": "https://i.html2img.com/image-1786092598870-921691.png",
    "filename": "image-1786092598870-921691.png",
    "format": "png",
    "dpi": 2,
    "log_id": "8a9dda43-5f42-4b93-8ff4-cd69ed32d402"
}
```

If the render fails, the webhook receives `{"status": "error", "error": "...", "filename": "...", "log_id": "..."}` instead - check `status` rather than assuming success. When the failure is something you can fix (an unreachable URL, a selector that never appeared), the payload also carries an `error_code` - the same codes as the [render error responses](#render-error-422) below.

### Account status response

`GET /api/me` returns an account snapshot rather than a render:
```json
{
    "email": "you@example.com",
    "plan": "1k",
    "plan_name": "1,000 Credits",
    "active": true,
    "free_plan": false,
    "credits_remaining": 850,
    "credits_reset_at": "2026-09-01T00:00:00+00:00"
}
```

Every field is explained in the [account status reference](https://html2img.com/docs/account/).

### Error Responses

#### Validation Error (400)
```json
{
    "error": "Validation failed",
    "code": "validation_error",
    "details": {
        "html": ["The html field is required."]
    }
}
```
The Templates API returns `422` for validation failures instead; the body shape is the same.

#### Authentication Error (401)
```json
{
    "error": "Invalid API key",
    "code": "invalid_api_key"
}
```
A missing `X-API-Key` header returns `missing_api_key`. Verify a key without spending a credit via [`GET /api/me`](https://html2img.com/docs/account/).

#### Out of Credits (402)
```json
{
    "error": "Insufficient credits",
    "code": "insufficient_credits",
    "credits_remaining": 0,
    "message": "You have used your free credits. Upgrade to a paid plan to keep rendering.",
    "upgrade_url": "https://app.html2img.com/plans"
}
```
On a paid plan the response carries `credits_reset_at` (your renewal date) instead of `upgrade_url`.

#### No Active Plan (403)
```json
{
    "error": "You must be subscribed to use this service",
    "code": "not_subscribed"
}
```

#### Render Error (422)
```json
{
    "error": "Render failed",
    "code": "url_not_found",
    "message": "That web address could not be found. Please check the URL for typos and make sure the site exists.",
    "id": "8a9dda43-5f42-4b93-8ff4-cd69ed32d402"
}
```
Returned when the render fails for a reason you can fix. `message` is safe to show to your own users. Possible `code` values:

| Code | Meaning |
| --- | --- |
| `url_not_found` | The URL's domain could not be resolved - usually a typo or a site that no longer exists |
| `url_unreachable` | The domain resolved but the page would not load (SSL failure, connection reset, too many redirects) |
| `connection_refused` | The site refused the connection - it may be down or blocking automated requests |
| `page_load_timeout` | The page took too long to load |
| `selector_timeout` | `wait_for_selector` never appeared within the time limit |
| `selector_not_found` | The `selector` element does not exist on the page |
| `screenshot_too_large` | The requested capture exceeds the size limits - reduce width, height or DPI |

#### Timeout Error (504)
```json
{
    "error": "Request timed out",
    "code": "timeout_error",
    "message": "Render job exceeded the allotted time. Consider passing webhook_url for large captures.",
    "id": "8a9dda43-5f42-4b93-8ff4-cd69ed32d402"
}
```
A timeout waiting for the renderer itself returns the same status with `code: "api_timeout_error"` and no `id`.

#### Service Error (500)
```json
{
    "error": "Service error",
    "code": "service_error",
    "message": "An internal error occurred while rendering.",
    "id": "8a9dda43-5f42-4b93-8ff4-cd69ed32d402"
}
```
A bug on our side - retry once, and if it persists [contact support](https://html2img.com/contact/) quoting the `id`.

## Next Steps

- [Read the OpenAPI specification](https://html2img.com/openapi.json) - the whole API as one machine-readable document
- [Set up authentication](https://html2img.com/docs/authentication/)
- [Install an official SDK](https://html2img.com/integrations/) - [PHP](https://html2img.com/integrations/php/), [Laravel](https://html2img.com/integrations/laravel/), [JavaScript](https://html2img.com/integrations/javascript/), [Python](https://html2img.com/integrations/python/) or [Ruby](https://html2img.com/integrations/ruby/)
- [See every integration](https://html2img.com/integrations/)
- [Browse the templates gallery](https://html2img.com/templates/)
- [Read the parameters reference](https://html2img.com/docs/parameters/)
