# Webpage to PDF

Use this endpoint to render public webpages into print-ready PDFs while keeping auth, polling, and downloads consistent with the rest of the DocuShell API.

Source: https://docs.docushell.com/webpage-to-pdf
Category: Reference
Read time: 5 min

## Related

- [Getting started](/getting-started.md): Shared request flow and error envelope.
- [API overview](/index.md): See where URL rendering fits into the full endpoint map.

## What It Does

DocuShell loads the target URL in an isolated rendering environment, applies optional viewport and timing settings, and returns the final PDF through the shared jobs API.

## Endpoint Reference

- Method: `POST`
- Path: `/v1/url/render`
- Auth: Bearer token required.
- Idempotency: Supports optional `Idempotency-Key` replay protection.
- Content type: `application/json`

Queue a URL capture job that renders a public webpage into a PDF.

### Headers

| Name | Type | Required | Location | Description |
| --- | --- | --- | --- | --- |
| Authorization | Bearer <API_KEY> | Yes | header | User-owned API key created in the DocuShell dashboard. |
| Idempotency-Key | string | No | header | Recommended for safely retrying submit requests without creating duplicate jobs. |
| Content-Type | application/json | Yes | header | JSON request body. |

### Request Fields

| Name | Type | Required | Location | Description |
| --- | --- | --- | --- | --- |
| url | string | Yes | body | Public URL to capture. |
| width | number | No | body | Optional viewport width in pixels. |
| height | number | No | body | Optional viewport height in pixels. |
| fullHeight | boolean | No | body | Render the full page height instead of a fixed viewport capture. |
| cleanup | boolean | No | body | Best-effort clean capture mode for common cookie banners, popups, ads, and chat widgets. Defaults to true. |
| waitForSelector | string | No | body | Optional CSS selector to wait for before printing. |
| delay | number | No | body | Optional post-load delay in milliseconds before rendering. |
| landscape | boolean | No | body | Render the PDF in landscape orientation. |
| grayscale | boolean | No | body | Produce a grayscale PDF. |

### Request Notes

- Private networks, metadata endpoints, and intranet-style targets are blocked by URL security validation before rendering.
- Clean capture is best-effort. Set `cleanup` to `false` to render the page without banner, popup, ad, or chat-widget cleanup.

### Sample Requests

#### Render a public page

```bash
curl -X POST "https://api.docushell.com/api/v1/url/render" \
  -H "Authorization: Bearer YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: url-demo-001" \
  -d '{
    "url": "https://docushell.com/pricing",
    "width": 1440,
    "height": 960,
    "fullHeight": true,
    "cleanup": true,
    "waitForSelector": "main",
    "delay": 1500
  }'
```

### Queued Response

#### Queued response

```json
{
  "job_id": "job_01JX8Y5YJ2M2D8N1AQ5F7Q3KVT",
  "status": "queued",
  "cost": 1500,
  "service": "webpage-to-pdf",
  "request_id": "req_01JX8Y62XCDNZ2BM7TBM2M9Q8E",
  "links": {
    "status": "/v1/jobs/job_01JX8Y5YJ2M2D8N1AQ5F7Q3KVT",
    "download": "/v1/jobs/job_01JX8Y5YJ2M2D8N1AQ5F7Q3KVT/download"
  }
}
```

### Status Response

#### Status response

```json
{
  "job_id": "job_01JX8Y5YJ2M2D8N1AQ5F7Q3KVT",
  "status": "done",
  "service": "webpage-to-pdf",
  "request_id": "req_01JX8Y62XCDNZ2BM7TBM2M9Q8E",
  "result": {
    "filename": "quarterly-report.pdf",
    "sizeBytes": 184322,
    "download": "/v1/jobs/job_01JX8Y5YJ2M2D8N1AQ5F7Q3KVT/download"
  },
  "completed_at": "2026-04-24T10:12:56.145Z",
  "links": {
    "status": "/v1/jobs/job_01JX8Y5YJ2M2D8N1AQ5F7Q3KVT",
    "download": "/v1/jobs/job_01JX8Y5YJ2M2D8N1AQ5F7Q3KVT/download"
  }
}
```

Completed jobs return a public download link and keep the file streaming URL behind the gateway. Downloads use application/pdf.

### Poll And Download

- Submit a URL render job, then poll the shared jobs endpoint until the job finishes.
- Completed jobs stream a PDF from the shared download route.

### Failure Notes

- Missing or invalid `url` fields return `invalid_request`.
- Blocked private-network URLs are rejected by the rendering service before work is queued.
- `backend_unavailable` indicates the render backend could not be reached.

### Error Examples

#### Missing URL field

- Status: `400`
- Code: `invalid_request`

The public lane requires a non-empty `url` string.

##### 400 error

```json
{
  "error": {
    "code": "invalid_request",
    "message": "A valid url field is required.",
    "type": "invalid_request_error",
    "request_id": "req_01JX8Y62XCDNZ2BM7TBM2M9Q8E"
  }
}
```

#### Authentication failure

- Status: `401`
- Code: `invalid_api_key`

Returned when the bearer token is missing, revoked, expired, or not allowed to use the API lane.

##### 401 error

```json
{
  "error": {
    "code": "invalid_api_key",
    "message": "Invalid API key.",
    "type": "auth_error",
    "request_id": "req_01JX8Y62XCDNZ2BM7TBM2M9Q8E"
  }
}
```

#### Idempotency conflict

- Status: `409`
- Code: `idempotency_key_reused`

Returned when the same Idempotency-Key is reused with a different payload than the original request.

##### 409 error

```json
{
  "error": {
    "code": "idempotency_key_reused",
    "message": "This Idempotency-Key was already used with a different request.",
    "type": "invalid_request_error",
    "request_id": "req_01JX8Y62XCDNZ2BM7TBM2M9Q8E"
  }
}
```

#### Webhook access disabled

- Status: `403`
- Code: `webhook_access_disabled`

Returned when a Starter API key submits webhook fields. Pro, Growth, and Scale include webhooks.

##### 403 error

```json
{
  "error": {
    "code": "webhook_access_disabled",
    "message": "Webhooks are available on Pro, Growth, and Scale. Starter includes API access without webhooks.",
    "type": "billing_error",
    "request_id": "req_01JX8Y62XCDNZ2BM7TBM2M9Q8E"
  }
}
```

#### Rate limit

- Status: `429`
- Code: `rate_limit_exceeded`

Returned when the API key or caller fingerprint exceeds the configured request rate.

##### 429 error

```json
{
  "error": {
    "code": "rate_limit_exceeded",
    "message": "Rate limit exceeded.",
    "type": "rate_limit_error",
    "request_id": "req_01JX8Y62XCDNZ2BM7TBM2M9Q8E"
  }
}
```
