# cloudlayer.io — Complete Documentation > Platform for captures, documents, and forms: capture reachable URLs, design templates visually or in HTML, merge data, and generate PDFs and images through repeatable workflows. This document inlines every published documentation page. The index version lives at https://cloudlayer.io/llms.txt ## API Reference ### API Overview Source: https://cloudlayer.io/docs/api-overview/ # API Overview The cloudlayer.io API lets you generate PDFs and images from HTML, URLs, and templates. All endpoints follow REST conventions and return standard HTTP status codes. ## Base URL All API requests are made to:
``` https://api.cloudlayer.io/v1 ``` Every endpoint path in this documentation is relative to this base URL. For example, `POST /v1/html/pdf` means you send your request to `https://api.cloudlayer.io/v1/html/pdf`.
``` https://api.cloudlayer.io/v2 ``` Every endpoint path in this documentation is relative to this base URL. For example, `POST /v2/html/pdf` means you send your request to `https://api.cloudlayer.io/v2/html/pdf`.
--- ## Authentication Every request must include your API key in the `X-API-Key` header. You can find and manage your API keys in the [cloudlayer.io dashboard](https://beta-app.cloudlayer.io). ``` X-API-Key: your-api-key-here ``` Requests without a valid API key receive a `401 Unauthorized` response. ### Example: Authenticated Request
**cURL** ```bash curl -X GET "https://api.cloudlayer.io/v1/account" \ -H "X-API-Key: your-api-key-here" ``` **JavaScript (fetch)** ```javascript const response = await fetch("https://api.cloudlayer.io/v1/account", { method: "GET", headers: { "X-API-Key": "your-api-key-here", }, }); const data = await response.json(); ``` **Python (requests)** ```python import requests response = requests.get( "https://api.cloudlayer.io/v1/account", headers={"X-API-Key": "your-api-key-here"}, ) data = response.json() ```
**cURL** ```bash curl -X GET "https://api.cloudlayer.io/v2/account" \ -H "X-API-Key: your-api-key-here" ``` **JavaScript (fetch)** ```javascript const response = await fetch("https://api.cloudlayer.io/v2/account", { method: "GET", headers: { "X-API-Key": "your-api-key-here", }, }); const data = await response.json(); ``` **Python (requests)** ```python import requests response = requests.get( "https://api.cloudlayer.io/v2/account", headers={"X-API-Key": "your-api-key-here"}, ) data = response.json() ```
> **Security tip:** Never expose your API key in client-side code. Always call the cloudlayer.io API from your server. --- ## Content Types Most endpoints accept JSON request bodies. Set the `Content-Type` header accordingly: | Scenario | Content-Type | | --- | --- | | JSON body (most endpoints) | `application/json` | | Multipart form data (template uploads) | `multipart/form-data` | | GET requests | No `Content-Type` needed |
Response content types depend on the endpoint: | Scenario | Response Content-Type | | --- | --- | | Document generation | `application/pdf`, `image/png`, `image/jpeg`, or `image/webp` | | Account, jobs, assets, storage | `application/json` | All document generation responses return the raw file binary directly.
All v2 responses return JSON: | Scenario | Response Content-Type | | --- | --- | | Document generation (async — default) | `application/json` (job metadata) | | Document generation (sync, `async: false`) | `application/json` (completed job with asset URL) | | Account, jobs, assets, storage | `application/json` | v2 always returns JSON with job metadata. Only v1 returns raw binary file content directly.
--- ## Synchronous vs. Asynchronous Requests
All document generation requests in v1 are processed **synchronously**. The API waits for the document to be generated and returns the raw file binary (PDF or image) directly in the response body. ```bash curl -X POST "https://api.cloudlayer.io/v1/html/pdf" \ -H "X-API-Key: your-api-key-here" \ -H "Content-Type: application/json" \ -d '{"html": "PGgxPkhlbGxvIFdvcmxkPC9oMT4="}' \ --output result.pdf ``` The response is the raw PDF binary. Use `--output` (cURL) or handle the response as a binary stream. > **Tip:** For large or complex documents that take a long time to render, consider switching to the v2 API which supports asynchronous processing.
By default, document generation endpoints in v2 process your request **asynchronously**. The API returns a JSON response immediately with the job ID, and the generated file is stored and accessible via the [Assets](/docs/assets/) endpoint once processing completes. ### Asynchronous (default) ```bash curl -X POST "https://api.cloudlayer.io/v2/html/pdf" \ -H "X-API-Key: your-api-key-here" \ -H "Content-Type: application/json" \ -d '{"html": "PGgxPkhlbGxvIFdvcmxkPC9oMT4="}' ``` Returns a JSON response immediately: ```json { "id": "1705312200000", "status": "pending" } ``` Poll the [Jobs](/docs/jobs/) endpoint or use [Webhooks](/docs/webhooks/) to be notified when the job completes. Once complete, retrieve the result from the [Assets](/docs/assets/) endpoint. ### Synchronous To process a request synchronously, include `"async": false` in your request body. The API still returns JSON, but waits for processing to complete before responding: ```bash curl -X POST "https://api.cloudlayer.io/v2/html/pdf" \ -H "X-API-Key: your-api-key-here" \ -H "Content-Type: application/json" \ -d '{"html": "PGgxPkhlbGxvIFdvcmxkPC9oMT4=", "async": false}' ``` The response is JSON with the completed job details, including the asset URL for downloading the generated file. > **Note:** In v2, `storage` also defaults to `true`. When `async` is `true` (the default), the generated file is automatically stored and accessible via the Assets endpoint. You can set `"storage": false` to disable storage, but this requires `"async": false` as well.
--- ## Rate Limits All accounts are rate-limited to **60 requests per 60 seconds**. This limit applies to all endpoints and all plans equally. Exceeding the limit returns a `429 Too Many Requests` response. Separately, your plan has a **usage quota** — a total number of API calls allowed per billing period. The `cl-calls-remaining` response header shows how many calls remain in your current period. | Header | Description | | --- | --- | | `cl-calls-remaining` | Number of API calls remaining in your current billing period | | `Retry-After` | Seconds to wait before retrying, sent on a `429` response | **Best practice:** Implement retry logic with exponential backoff for `429` responses, honoring `Retry-After` when it is present. Monitor `cl-calls-remaining` to track usage against your plan quota. ### Concurrency Rate limiting and concurrency are different controls and you can meet either one first. The rate limit above caps how many requests you may *send* in a window. Concurrency caps how many renders run *at the same time*, and unlike the rate limit it varies by plan: extra requests queue rather than failing, so a batch finishes more slowly rather than returning `429`. The concurrency included with each plan is listed on the [pricing page](https://cloudlayer.io/pricing/) alongside the render allowance. --- ## HTTP Status Codes | Code | Meaning | Description | | --- | --- | --- | | `200` | OK | Request succeeded. Response body contains the result. | | `400` | Bad Request | The request body is malformed or missing required parameters. | | `401` | Unauthorized | Missing or invalid API key, or account has exceeded its usage limits. | | `404` | Not Found | The requested resource does not exist (e.g., invalid route). | | `429` | Too Many Requests | Rate limit exceeded (60 requests per 60 seconds). Retry with exponential backoff. | | `500` | Internal Server Error | Something went wrong on our end. Contact support if it persists. | --- ## Error Response Format When an error occurs, the API returns a JSON body with the status code and a message: ```json { "statusCode": 400, "message": "The 'html' field is required and must be a base64-encoded string." } ``` | Field | Type | Description | | --- | --- | --- | | `statusCode` | number | The HTTP status code (matches the response status). | | `message` | string | A human-readable description of the error. | Some error responses may also include a `status` field with the value `"error"`: ```json { "status": "error", "message": "Generation failed: page timeout exceeded." } ``` --- ## Common Request Parameters Several parameters appear across multiple document generation endpoints. These are documented in detail on each endpoint page, but here is a quick reference:
### Async & Storage Parameters (v2 only) | Parameter | Type | Default | Description | | --- | --- | --- | --- | | `async` | boolean | `true` | Process the request asynchronously. When `true`, the API returns immediately with a job ID. Set to `false` to wait for processing to complete before the API responds (response is still JSON). | | `storage` | boolean | `true` | Store the generated file in cloudlayer.io storage. When `true`, the file is accessible via the [Assets](/docs/assets/) endpoint. Requires a plan that supports storage. |
### Base Parameters (all generation endpoints) | Parameter | Type | Description | | --- | --- | --- | | `autoScroll` | boolean | Scroll the page to trigger lazy-loaded content before capture. | | `delay` | number | Wait time in milliseconds after the page loads before capturing. | | `filename` | string | Set the `Content-Disposition` filename for the response. | | `timeout` | number | Maximum time in milliseconds to wait for the page to load. | | `viewPort` | object | Configure the browser viewport dimensions and device emulation. | | `waitUntil` | string | When to consider navigation complete (`load`, `domcontentloaded`, `networkidle0`, `networkidle2`). | ### PDF-Specific Parameters | Parameter | Type | Description | | --- | --- | --- | | `format` | string | Page size (`letter`, `legal`, `tabloid`, `a0`-`a6`, etc.). | | `margin` | object | Page margins with unit support (`px`, `in`, `cm`, `mm`). | | `landscape` | boolean | Use landscape orientation. | | `printBackground` | boolean | Include background colors and images. | | `headerTemplate` | object | Custom header for each page. | | `footerTemplate` | object | Custom footer for each page. | ### Image-Specific Parameters | Parameter | Type | Description | | --- | --- | --- | | `imageType` | string | Output format: `png`, `jpg`, `jpeg`, `webp`, or `svg`. | | `transparent` | boolean | Render with a transparent background (PNG and WebP only). | | `trim` | boolean | Trim whitespace from the edges of the image. | --- ## List Results List endpoints (`GET /jobs`, `GET /assets`) return the 10 most recent items, ordered by creation date (newest first). There are no pagination query parameters. --- ## Next Steps - [HTML to PDF](/docs/html-to-pdf/) — Generate PDFs from raw HTML - [URL to PDF](/docs/url-to-pdf/) — Capture any web page as a PDF - [Template to PDF](/docs/template-to-pdf/) — Generate PDFs from reusable templates - [HTML to Image](/docs/html-to-image/) — Generate images from raw HTML - [URL to Image](/docs/url-to-image/) — Capture any web page as an image - [Template to Image](/docs/template-to-image/) — Generate images from reusable templates ### HTML to PDF Source: https://cloudlayer.io/docs/html-to-pdf/ # HTML to PDF Generate a PDF document from raw HTML content. The HTML is sent as a base64-encoded string in the request body. ## Endpoint
``` POST /v1/html/pdf ``` All requests are processed synchronously. The response body is the raw PDF file.
``` POST /v2/html/pdf ``` Requests default to **asynchronous** processing (`async: true`, `storage: true`). Add `"async": false` to receive the raw PDF directly. See [Sync vs. Async](/docs/api-overview/#synchronous-vs-asynchronous-requests) for details.
--- ## Quick Start Encode your HTML as base64 and send it in the `html` field:
**cURL** ```bash # Base64 encode your HTML HTML_BASE64=$(echo '

Hello World

Generated by cloudlayer.io

' | base64) curl -X POST "https://api.cloudlayer.io/v1/html/pdf" \ -H "X-API-Key: your-api-key-here" \ -H "Content-Type: application/json" \ -d "{\"html\": \"$HTML_BASE64\"}" \ --output result.pdf ``` **JavaScript (fetch)** ```javascript const html = `

Hello World

Generated by cloudlayer.io

`; const response = await fetch("https://api.cloudlayer.io/v1/html/pdf", { method: "POST", headers: { "X-API-Key": "your-api-key-here", "Content-Type": "application/json", }, body: JSON.stringify({ html: btoa(html), }), }); const pdf = await response.arrayBuffer(); // Save to file, send to client, etc. ``` **Python (requests)** ```python import base64 import requests html = """

Hello World

Generated by cloudlayer.io

""" response = requests.post( "https://api.cloudlayer.io/v1/html/pdf", headers={ "X-API-Key": "your-api-key-here", "Content-Type": "application/json", }, json={ "html": base64.b64encode(html.encode()).decode(), }, ) with open("result.pdf", "wb") as f: f.write(response.content) ```
**cURL** ```bash # Base64 encode your HTML HTML_BASE64=$(echo '

Hello World

Generated by cloudlayer.io

' | base64) curl -X POST "https://api.cloudlayer.io/v2/html/pdf" \ -H "X-API-Key: your-api-key-here" \ -H "Content-Type: application/json" \ -d "{\"html\": \"$HTML_BASE64\"}" ``` Response: ```json { "id": "abc123", "status": "pending" } ``` **JavaScript (fetch)** ```javascript const html = `

Hello World

Generated by cloudlayer.io

`; const response = await fetch("https://api.cloudlayer.io/v2/html/pdf", { method: "POST", headers: { "X-API-Key": "your-api-key-here", "Content-Type": "application/json", }, body: JSON.stringify({ html: btoa(html), }), }); const { id, status } = await response.json(); // Poll job status or use webhooks to know when the PDF is ready ``` **Python (requests)** ```python import base64 import requests html = """

Hello World

Generated by cloudlayer.io

""" response = requests.post( "https://api.cloudlayer.io/v2/html/pdf", headers={ "X-API-Key": "your-api-key-here", "Content-Type": "application/json", }, json={ "html": base64.b64encode(html.encode()).decode(), }, ) job = response.json() # Poll job status or use webhooks to know when the PDF is ready ``` > **Tip:** v2 always returns JSON. Use the job `id` to poll the [Jobs](/docs/jobs/) endpoint or configure [Webhooks](/docs/webhooks/) for completion notifications. The generated file is accessible via the [Assets](/docs/assets/) endpoint. Add `"async": false` to wait for processing to complete — the response is still JSON but includes the completed job details.
--- ## Parameters ### Required | Parameter | Type | Description | | --- | --- | --- | | `html` | string | **Required.** Base64-encoded HTML content. The HTML can include inline CSS, `
Acme Corp
Invoice #1042 — 2024-01-15
ItemQtyPriceTotal
Widget A10$25.00$250.00
Widget B5$40.00$200.00
Service Fee1$75.00$75.00
Total: $525.00
HTML ) HTML_BASE64=$(echo "$HTML" | base64 -w 0) curl -X POST "https://api.cloudlayer.io/v2/html/pdf" \ -H "X-API-Key: your-api-key-here" \ -H "Content-Type: application/json" \ -d "{ \"html\": \"$HTML_BASE64\", \"format\": \"letter\", \"margin\": { \"top\": \"0.75in\", \"bottom\": \"1in\", \"left\": \"0.75in\", \"right\": \"0.75in\" }, \"printBackground\": true, \"footerTemplate\": { \"selector\": \"#page-footer\", \"method\": \"template\", \"style\": \"font-size: 9px; width: 100%; text-align: center; color: #999;\" }, \"filename\": \"invoice-1042.pdf\" }" ``` **JavaScript (fetch)** ```javascript const html = `
Acme Corp
Invoice #1042
ItemQtyPriceTotal
Widget A10$25.00$250.00
Widget B5$40.00$200.00
Total: $450.00
`; const response = await fetch("https://api.cloudlayer.io/v2/html/pdf", { method: "POST", headers: { "X-API-Key": "your-api-key-here", "Content-Type": "application/json", }, body: JSON.stringify({ html: btoa(html), format: "letter", margin: { top: "0.75in", bottom: "0.75in", left: "0.75in", right: "0.75in", }, printBackground: true, filename: "invoice-1042.pdf", }), }); const job = await response.json(); // job.id contains the job ID — poll or use webhooks for completion ``` **Python (requests)** ```python import base64 import requests html = """

Invoice #1042

ItemQtyPriceTotal
Widget A10$25.00$250.00
Widget B5$40.00$200.00
Total: $450.00
""" response = requests.post( "https://api.cloudlayer.io/v2/html/pdf", headers={ "X-API-Key": "your-api-key-here", "Content-Type": "application/json", }, json={ "html": base64.b64encode(html.encode()).decode(), "format": "letter", "margin": { "top": "0.75in", "bottom": "0.75in", "left": "0.75in", "right": "0.75in", }, "printBackground": True, "filename": "invoice-1042.pdf", }, ) job = response.json() # job["id"] contains the job ID — poll or use webhooks for completion ``` --- ## Tips - **External resources:** Your HTML can reference external CSS, fonts, and images via URLs. Make sure they are publicly accessible, or the renderer will not be able to load them. - **Web fonts:** Use `` tags to load Google Fonts or other web fonts. Set `waitUntil` to `"networkidle0"` to ensure fonts finish loading before rendering. - **Large documents:** For documents with many pages, increase the `timeout` value and consider using async mode. - **Print styles:** Use `@media print` CSS rules to control what appears in the PDF. The renderer respects print media queries. - **Base64 encoding:** Make sure your base64 encoding does not include line breaks. Use `base64 -w 0` on Linux or equivalent. ### URL to PDF Source: https://cloudlayer.io/docs/url-to-pdf/ # URL to PDF Generate a PDF from a live web page URL. Supports both simple GET requests and full-featured POST requests with authentication, cookies, and batch processing. ## Endpoints The **GET** endpoint is a convenience method for simple captures with minimal configuration. The **POST** endpoint supports the full range of parameters.
``` GET /v1/url/pdf POST /v1/url/pdf ``` All v1 requests are processed synchronously. The response body is the raw PDF file.
``` GET /v2/url/pdf POST /v2/url/pdf ``` v2 requests default to **asynchronous** processing (`async: true`, `storage: true`). Add `"async": false` to receive the raw PDF directly. See [Sync vs. Async](/docs/api-overview/#synchronous-vs-asynchronous-requests) for details.
--- ## Quick Start (GET) The simplest way to capture a web page as a PDF — just pass the URL as a query parameter:
**cURL** ```bash curl -X GET "https://api.cloudlayer.io/v1/url/pdf?url=https://example.com" \ -H "X-API-Key: your-api-key-here" \ --output example.pdf ``` **JavaScript (fetch)** ```javascript const url = encodeURIComponent("https://example.com"); const response = await fetch( `https://api.cloudlayer.io/v1/url/pdf?url=${url}`, { headers: { "X-API-Key": "your-api-key-here", }, } ); const pdf = await response.arrayBuffer(); ``` **Python (requests)** ```python import requests response = requests.get( "https://api.cloudlayer.io/v1/url/pdf", params={"url": "https://example.com"}, headers={"X-API-Key": "your-api-key-here"}, ) with open("example.pdf", "wb") as f: f.write(response.content) ```
**cURL** ```bash curl -X GET "https://api.cloudlayer.io/v2/url/pdf?url=https://example.com" \ -H "X-API-Key: your-api-key-here" ``` Response: ```json { "id": "abc123", "status": "pending" } ``` **JavaScript (fetch)** ```javascript const url = encodeURIComponent("https://example.com"); const response = await fetch( `https://api.cloudlayer.io/v2/url/pdf?url=${url}`, { headers: { "X-API-Key": "your-api-key-here", }, } ); const job = await response.json(); ``` **Python (requests)** ```python import requests response = requests.get( "https://api.cloudlayer.io/v2/url/pdf", params={"url": "https://example.com"}, headers={"X-API-Key": "your-api-key-here"}, ) job = response.json() ```
--- ## Quick Start (POST) Use POST for full control over the output:
**cURL** ```bash curl -X POST "https://api.cloudlayer.io/v1/url/pdf" \ -H "X-API-Key: your-api-key-here" \ -H "Content-Type: application/json" \ -d '{ "url": "https://example.com", "format": "a4", "margin": { "top": "1in", "bottom": "1in", "left": "0.75in", "right": "0.75in" }, "printBackground": true, "waitUntil": "networkidle0" }' \ --output example.pdf ``` **JavaScript (fetch)** ```javascript const response = await fetch("https://api.cloudlayer.io/v1/url/pdf", { method: "POST", headers: { "X-API-Key": "your-api-key-here", "Content-Type": "application/json", }, body: JSON.stringify({ url: "https://example.com", format: "a4", margin: { top: "1in", bottom: "1in", left: "0.75in", right: "0.75in" }, printBackground: true, waitUntil: "networkidle0", }), }); const pdf = await response.arrayBuffer(); ``` **Python (requests)** ```python import requests response = requests.post( "https://api.cloudlayer.io/v1/url/pdf", headers={ "X-API-Key": "your-api-key-here", "Content-Type": "application/json", }, json={ "url": "https://example.com", "format": "a4", "margin": {"top": "1in", "bottom": "1in", "left": "0.75in", "right": "0.75in"}, "printBackground": True, "waitUntil": "networkidle0", }, ) with open("example.pdf", "wb") as f: f.write(response.content) ```
**cURL** ```bash curl -X POST "https://api.cloudlayer.io/v2/url/pdf" \ -H "X-API-Key: your-api-key-here" \ -H "Content-Type: application/json" \ -d '{ "url": "https://example.com", "format": "a4", "margin": { "top": "1in", "bottom": "1in", "left": "0.75in", "right": "0.75in" }, "printBackground": true, "waitUntil": "networkidle0" }' ``` **JavaScript (fetch)** ```javascript const response = await fetch("https://api.cloudlayer.io/v2/url/pdf", { method: "POST", headers: { "X-API-Key": "your-api-key-here", "Content-Type": "application/json", }, body: JSON.stringify({ url: "https://example.com", format: "a4", margin: { top: "1in", bottom: "1in", left: "0.75in", right: "0.75in" }, printBackground: true, waitUntil: "networkidle0", }), }); const job = await response.json(); ``` **Python (requests)** ```python import requests response = requests.post( "https://api.cloudlayer.io/v2/url/pdf", headers={ "X-API-Key": "your-api-key-here", "Content-Type": "application/json", }, json={ "url": "https://example.com", "format": "a4", "margin": {"top": "1in", "bottom": "1in", "left": "0.75in", "right": "0.75in"}, "printBackground": True, "waitUntil": "networkidle0", }, ) job = response.json() ``` > **Tip:** v2 always returns JSON. Use the job `id` to poll the [Jobs](/docs/jobs/) endpoint or configure [Webhooks](/docs/webhooks/) for completion notifications. The generated file is accessible via the [Assets](/docs/assets/) endpoint. Add `"async": false` to wait for processing to complete — the response is still JSON but includes the completed job details.
--- ## GET Parameters The GET endpoint accepts these query parameters: | Parameter | Type | Required | Default | Description | | --- | --- | --- | --- | --- | | `url` | string | Yes | — | The URL of the web page to capture. Must be URL-encoded. | | `timeout` | number | No | `30000` | Maximum time in milliseconds to wait for the page to load. | --- ## POST Parameters ### URL Parameters | Parameter | Type | Required | Default | Description | | --- | --- | --- | --- | --- | | `url` | string | Yes | — | The URL of the web page to capture. | | `authentication` | object | No | — | Credentials for HTTP Basic Authentication. See [authentication](#authentication-object). | | `cookies` | array | No | — | Cookies to set before navigating to the URL. See [cookies](#cookies-array). | | `batch` | object | No | — | Object with a `urls` property containing an array of URLs to combine into a single multi-page PDF. See [Batch Processing](#batch-processing). | ### Base Parameters These parameters control page loading behavior, viewport configuration, and general output settings. | Parameter | Type | Default | Description | | --- | --- | --- | --- | | `autoScroll` | boolean | `false` | Scroll the page to the bottom before capturing. Triggers lazy-loaded content. | | `delay` | number | `0` | Wait time in milliseconds after the page loads before generating the PDF. | | `filename` | string | — | Set the `Content-Disposition` filename on the response. | | `generatePreview` | object | — | Generate a thumbnail preview image. See [HTML to PDF — generatePreview](/docs/html-to-pdf/#generatepreview-object). | | `height` | string | — | Override page height with a CSS value (e.g., `"11in"`, `"297mm"`). | | `width` | string | — | Override page width with a CSS value (e.g., `"8.5in"`, `"210mm"`). | | `inline` | boolean | `false` | Display the PDF inline in the browser instead of triggering a download. | | `landscape` | boolean | `false` | Generate the PDF in landscape orientation. | | `preferCSSPageSize` | boolean | `false` | Use the CSS `@page` size instead of the `format` parameter. | | `projectId` | string | — | Associate this request with a project for dashboard organization. | | `scale` | number | `1` | Page rendering scale (`0.1` to `2`). | | `timeout` | number | `30000` | Maximum time in milliseconds to wait for page load. | | `timeZone` | string | — | Browser timezone (IANA name, e.g., `"America/New_York"`). | | `viewPort` | object | — | Browser viewport configuration. See [HTML to PDF — viewPort](/docs/html-to-pdf/#viewport-object). | | `waitForSelector` | object | — | Wait for a CSS selector before generating. See [HTML to PDF — waitForSelector](/docs/html-to-pdf/#waitforselector-object). | | `waitUntil` | string | `"networkidle2"` | Navigation completion strategy: `"load"`, `"domcontentloaded"`, `"networkidle0"`, `"networkidle2"`. | ### PDF Parameters | Parameter | Type | Default | Description | | --- | --- | --- | --- | | `pageRanges` | string | — | Page ranges to include (e.g., `"1-5"`, `"1,3,5-7"`). | | `format` | string | `"letter"` | Page size: `"letter"`, `"legal"`, `"tabloid"`, `"ledger"`, `"a0"` through `"a6"`. | | `margin` | object | — | Page margins with CSS units. See [HTML to PDF — margin](/docs/html-to-pdf/#margin-object). | | `printBackground` | boolean | `true` | Include background colors and images in the PDF. | | `headerTemplate` | object | — | Custom page header. See [HTML to PDF — headerTemplate](/docs/html-to-pdf/#headertemplate--footertemplate-object). | | `footerTemplate` | object | — | Custom page footer. See [HTML to PDF — footerTemplate](/docs/html-to-pdf/#headertemplate--footertemplate-object). | --- ## Parameter Details ### `authentication` Object Provide HTTP Basic Authentication credentials. The browser will send these credentials when navigating to the URL. | Field | Type | Required | Description | | --- | --- | --- | --- | | `username` | string | Yes | The username for Basic Auth. | | `password` | string | Yes | The password for Basic Auth. | ```json { "url": "https://staging.example.com/report", "authentication": { "username": "admin", "password": "s3cur3p@ss" } } ``` ### `cookies` Array Set cookies in the browser before navigating to the URL. Each cookie is an object with the following fields: | Field | Type | Required | Default | Description | | --- | --- | --- | --- | --- | | `name` | string | Yes | — | Cookie name. | | `value` | string | Yes | — | Cookie value. | | `domain` | string | No | — | Domain the cookie applies to. Defaults to the URL's domain. | | `path` | string | No | `"/"` | URL path the cookie applies to. | | `expires` | number | No | — | Unix timestamp (seconds) when the cookie expires. Omit for a session cookie. | | `httpOnly` | boolean | No | `false` | Mark the cookie as HTTP-only (not accessible via JavaScript). | | `secure` | boolean | No | `false` | Mark the cookie as secure (only sent over HTTPS). | | `sameSite` | string | No | `"Lax"` | SameSite attribute: `"Strict"`, `"Lax"`, or `"None"`. | ```json { "url": "https://app.example.com/dashboard", "cookies": [ { "name": "session_id", "value": "abc123def456", "domain": "app.example.com", "path": "/", "secure": true, "httpOnly": true }, { "name": "theme", "value": "dark", "domain": "app.example.com" } ] } ``` > **Use case:** Cookies let you capture authenticated pages (dashboards, admin panels, user profiles) by passing the user's session cookie. This is often simpler than Basic Auth for applications with cookie-based session management. --- ## Batch Processing Combine multiple URLs into a single multi-page PDF. Each URL is rendered as a separate section in the output document. Pass an object with a `urls` array in the `batch` parameter: ```json { "batch": { "urls": [ "https://example.com/report/page-1", "https://example.com/report/page-2", "https://example.com/report/page-3" ] }, "format": "a4", "printBackground": true, "waitUntil": "networkidle0" } ``` > **Note:** When using `batch`, the `url` parameter is ignored. All other parameters (format, margin, viewport, etc.) apply to every URL in the batch. ### Batch Example **cURL** ```bash curl -X POST "https://api.cloudlayer.io/v2/url/pdf" \ -H "X-API-Key: your-api-key-here" \ -H "Content-Type: application/json" \ -d '{ "batch": { "urls": [ "https://example.com/chapter-1", "https://example.com/chapter-2", "https://example.com/chapter-3" ] }, "format": "letter", "margin": { "top": "1in", "bottom": "1in", "left": "1in", "right": "1in" }, "printBackground": true, "filename": "full-report.pdf" }' ``` **JavaScript (fetch)** ```javascript const response = await fetch("https://api.cloudlayer.io/v2/url/pdf", { method: "POST", headers: { "X-API-Key": "your-api-key-here", "Content-Type": "application/json", }, body: JSON.stringify({ batch: { urls: [ "https://example.com/chapter-1", "https://example.com/chapter-2", "https://example.com/chapter-3", ], }, format: "letter", margin: { top: "1in", bottom: "1in", left: "1in", right: "1in" }, printBackground: true, filename: "full-report.pdf", }), }); const job = await response.json(); ``` **Python (requests)** ```python import requests response = requests.post( "https://api.cloudlayer.io/v2/url/pdf", headers={ "X-API-Key": "your-api-key-here", "Content-Type": "application/json", }, json={ "batch": { "urls": [ "https://example.com/chapter-1", "https://example.com/chapter-2", "https://example.com/chapter-3", ], }, "format": "letter", "margin": {"top": "1in", "bottom": "1in", "left": "1in", "right": "1in"}, "printBackground": True, "filename": "full-report.pdf", }, ) job = response.json() ``` --- ## Authentication Example Capture a page behind HTTP Basic Auth: **cURL** ```bash curl -X POST "https://api.cloudlayer.io/v2/url/pdf" \ -H "X-API-Key: your-api-key-here" \ -H "Content-Type: application/json" \ -d '{ "url": "https://staging.internal.example.com/admin/report", "authentication": { "username": "report-viewer", "password": "s3cur3p@ss" }, "format": "a4", "printBackground": true, "waitUntil": "networkidle0", "timeout": 60000 }' ``` --- ## Cookie Authentication Example Capture an authenticated dashboard by injecting session cookies: **cURL** ```bash curl -X POST "https://api.cloudlayer.io/v2/url/pdf" \ -H "X-API-Key: your-api-key-here" \ -H "Content-Type: application/json" \ -d '{ "url": "https://app.example.com/dashboard", "cookies": [ { "name": "auth_token", "value": "eyJhbGciOiJIUzI1NiIs...", "domain": "app.example.com", "secure": true, "httpOnly": true, "sameSite": "Lax" } ], "format": "letter", "landscape": true, "printBackground": true, "waitUntil": "networkidle0", "delay": 2000 }' ``` --- ## Tips - **Dynamic content:** Use `waitUntil: "networkidle0"` for SPAs and pages that fetch data via AJAX. Add a `delay` if content renders after API calls complete. - **Authenticated pages:** Prefer `cookies` over `authentication` for modern web apps with session-based auth. Use `authentication` for sites with HTTP Basic Auth (common in staging environments). - **Batch limits:** Batch processing is subject to your plan's timeout limits. For large batches, consider using async mode. - **CORS and firewalls:** The URL must be publicly accessible from cloudlayer.io servers. For internal/private URLs, consider using the [HTML to PDF](/docs/html-to-pdf/) endpoint instead. - **Mobile rendering:** Set `viewPort.isMobile` to `true` and `viewPort.width` to `375` (or similar) to capture the mobile version of a responsive site. ### Template to PDF Source: https://cloudlayer.io/docs/template-to-pdf/ # Template to PDF Generate a PDF from a template with dynamic data. Use predefined templates from the cloudlayer.io template gallery or provide your own custom HTML template. Templates support Nunjucks syntax for data binding. ## Endpoint
``` POST /v1/template/pdf ``` All requests are processed synchronously. The response body is the raw PDF file.
``` POST /v2/template/pdf ``` Requests default to **asynchronous** processing (`async: true`, `storage: true`). Add `"async": false` to receive the raw PDF directly. See [Sync vs. Async](/docs/api-overview/#synchronous-vs-asynchronous-requests) for details.
Supports two content types: | Content Type | Use Case | | --- | --- | | `application/json` | Inline templates (base64-encoded) or predefined template IDs with JSON data. | | `multipart/form-data` | Upload template files directly alongside JSON data. | --- ## Quick Start: Predefined Template Use a template from the cloudlayer.io gallery by referencing its `templateId`:
**cURL** ```bash curl -X POST "https://api.cloudlayer.io/v1/template/pdf" \ -H "X-API-Key: your-api-key-here" \ -H "Content-Type: application/json" \ -d '{ "templateId": "professional-invoice", "data": { "invoiceNumber": "INV-2024-001", "companyName": "Acme Corp", "customerName": "Jane Smith", "items": [ {"name": "Widget A", "quantity": 10, "price": 25.00}, {"name": "Widget B", "quantity": 5, "price": 40.00} ], "total": 450.00 } }' \ --output invoice.pdf ``` **JavaScript (fetch)** ```javascript const response = await fetch("https://api.cloudlayer.io/v1/template/pdf", { method: "POST", headers: { "X-API-Key": "your-api-key-here", "Content-Type": "application/json", }, body: JSON.stringify({ templateId: "professional-invoice", data: { invoiceNumber: "INV-2024-001", companyName: "Acme Corp", customerName: "Jane Smith", items: [ { name: "Widget A", quantity: 10, price: 25.0 }, { name: "Widget B", quantity: 5, price: 40.0 }, ], total: 450.0, }, }), }); const pdf = await response.arrayBuffer(); ``` **Python (requests)** ```python import requests response = requests.post( "https://api.cloudlayer.io/v1/template/pdf", headers={ "X-API-Key": "your-api-key-here", "Content-Type": "application/json", }, json={ "templateId": "professional-invoice", "data": { "invoiceNumber": "INV-2024-001", "companyName": "Acme Corp", "customerName": "Jane Smith", "items": [ {"name": "Widget A", "quantity": 10, "price": 25.00}, {"name": "Widget B", "quantity": 5, "price": 40.00}, ], "total": 450.00, }, }, ) with open("invoice.pdf", "wb") as f: f.write(response.content) ```
**cURL** ```bash curl -X POST "https://api.cloudlayer.io/v2/template/pdf" \ -H "X-API-Key: your-api-key-here" \ -H "Content-Type: application/json" \ -d '{ "templateId": "professional-invoice", "data": { "invoiceNumber": "INV-2024-001", "companyName": "Acme Corp", "customerName": "Jane Smith", "items": [ {"name": "Widget A", "quantity": 10, "price": 25.00}, {"name": "Widget B", "quantity": 5, "price": 40.00} ], "total": 450.00 } }' ``` **JavaScript (fetch)** ```javascript const response = await fetch("https://api.cloudlayer.io/v2/template/pdf", { method: "POST", headers: { "X-API-Key": "your-api-key-here", "Content-Type": "application/json", }, body: JSON.stringify({ templateId: "professional-invoice", data: { invoiceNumber: "INV-2024-001", companyName: "Acme Corp", customerName: "Jane Smith", items: [ { name: "Widget A", quantity: 10, price: 25.0 }, { name: "Widget B", quantity: 5, price: 40.0 }, ], total: 450.0, }, }), }); const job = await response.json(); ``` **Python (requests)** ```python import requests response = requests.post( "https://api.cloudlayer.io/v2/template/pdf", headers={ "X-API-Key": "your-api-key-here", "Content-Type": "application/json", }, json={ "templateId": "professional-invoice", "data": { "invoiceNumber": "INV-2024-001", "companyName": "Acme Corp", "customerName": "Jane Smith", "items": [ {"name": "Widget A", "quantity": 10, "price": 25.00}, {"name": "Widget B", "quantity": 5, "price": 40.00}, ], "total": 450.00, }, }, ) job = response.json() ``` > **Tip:** v2 always returns JSON. Use the job `id` to poll the [Jobs](/docs/jobs/) endpoint or configure [Webhooks](/docs/webhooks/) for completion notifications. The generated file is accessible via the [Assets](/docs/assets/) endpoint. Add `"async": false` to wait for processing to complete — the response is still JSON but includes the completed job details.
--- ## Quick Start: Custom Template Provide your own HTML template as a base64-encoded string: **cURL** ```bash TEMPLATE=$(cat <<'HTML'

{{title}}

Date: {{date}}
Prepared for: {{recipient}}
{{ content | safe }}
HTML ) TEMPLATE_BASE64=$(echo "$TEMPLATE" | base64 -w 0) curl -X POST "https://api.cloudlayer.io/v2/template/pdf" \ -H "X-API-Key: your-api-key-here" \ -H "Content-Type: application/json" \ -d "{ \"template\": \"$TEMPLATE_BASE64\", \"data\": { \"title\": \"Monthly Report\", \"date\": \"January 2024\", \"recipient\": \"Jane Smith\", \"content\": \"

This is the report content with HTML support.

\" } }" ``` **JavaScript (fetch)** ```javascript const template = `

{{title}}

Date: {{date}}
Prepared for: {{recipient}}
{{ content | safe }}
`; const response = await fetch("https://api.cloudlayer.io/v2/template/pdf", { method: "POST", headers: { "X-API-Key": "your-api-key-here", "Content-Type": "application/json", }, body: JSON.stringify({ template: btoa(template), data: { title: "Monthly Report", date: "January 2024", recipient: "Jane Smith", content: "

This is the report content with HTML support.

", }, }), }); const job = await response.json(); ``` **Python (requests)** ```python import base64 import requests template = """

{{title}}

Prepared for: {{recipient}}

{{ content | safe }}
""" response = requests.post( "https://api.cloudlayer.io/v2/template/pdf", headers={ "X-API-Key": "your-api-key-here", "Content-Type": "application/json", }, json={ "template": base64.b64encode(template.encode()).decode(), "data": { "title": "Monthly Report", "recipient": "Jane Smith", "content": "

Report content here.

", }, }, ) job = response.json() ``` --- ## Parameters ### Template Parameters You must provide either `templateId` (for a predefined template) or `template` (for a custom template), but not both. | Parameter | Type | Required | Default | Description | | --- | --- | --- | --- | --- | | `templateId` | string | Conditional | — | The ID of a predefined template from the cloudlayer.io template gallery. Browse available templates in the [dashboard](https://beta-app.cloudlayer.io). | | `template` | string | Conditional | — | A custom HTML template as a base64-encoded string. Supports Nunjucks syntax for data binding. | | `data` | object | No | `{}` | Key-value pairs of data to inject into the template. Keys correspond to Nunjucks placeholders in the template (e.g., `{{name}}` is populated by `data.name`). | ### Base Parameters | Parameter | Type | Default | Description | | --- | --- | --- | --- | | `autoScroll` | boolean | `false` | Scroll the page to trigger lazy-loaded content before capture. | | `delay` | number | `0` | Wait time in milliseconds after the page loads before generating. | | `filename` | string | — | Set the `Content-Disposition` filename on the response. | | `generatePreview` | object | — | Generate a thumbnail preview image. See [HTML to PDF — generatePreview](/docs/html-to-pdf/#generatepreview-object). | | `height` | string | — | Override page height with a CSS value. | | `width` | string | — | Override page width with a CSS value. | | `inline` | boolean | `false` | Display the PDF inline in the browser instead of downloading. | | `landscape` | boolean | `false` | Generate in landscape orientation. | | `preferCSSPageSize` | boolean | `false` | Use CSS `@page` size instead of `format`. | | `projectId` | string | — | Associate with a project for dashboard organization. | | `scale` | number | `1` | Page rendering scale (`0.1` to `2`). | | `timeout` | number | `30000` | Maximum page load time in milliseconds. | | `timeZone` | string | — | Browser timezone (IANA name). | | `viewPort` | object | — | Browser viewport configuration. See [HTML to PDF — viewPort](/docs/html-to-pdf/#viewport-object). | | `waitForSelector` | object | — | Wait for a CSS selector before generating. See [HTML to PDF — waitForSelector](/docs/html-to-pdf/#waitforselector-object). | | `waitUntil` | string | `"networkidle2"` | Navigation completion strategy. | ### PDF Parameters | Parameter | Type | Default | Description | | --- | --- | --- | --- | | `pageRanges` | string | — | Page ranges to include (e.g., `"1-5"`, `"1,3,5"`). | | `format` | string | `"letter"` | Page size: `"letter"`, `"legal"`, `"tabloid"`, `"ledger"`, `"a0"` through `"a6"`. | | `margin` | object | — | Page margins. See [HTML to PDF — margin](/docs/html-to-pdf/#margin-object). | | `printBackground` | boolean | `true` | Include background colors and images. | | `headerTemplate` | object | — | Custom page header. See [HTML to PDF — headerTemplate](/docs/html-to-pdf/#headertemplate--footertemplate-object). | | `footerTemplate` | object | — | Custom page footer. See [HTML to PDF — footerTemplate](/docs/html-to-pdf/#headertemplate--footertemplate-object). | --- ## Multipart Uploads For uploading template files directly (instead of base64-encoding), use `multipart/form-data`: **cURL** ```bash curl -X POST "https://api.cloudlayer.io/v2/template/pdf" \ -H "X-API-Key: your-api-key-here" \ -F "template=@./my-template.html" \ -F 'data={"invoiceNumber":"INV-001","total":450.00}' ``` **JavaScript (fetch)** ```javascript const formData = new FormData(); formData.append("template", new Blob([templateHtml], { type: "text/html" }), "template.html"); formData.append("data", JSON.stringify({ invoiceNumber: "INV-001", total: 450.00, })); const response = await fetch("https://api.cloudlayer.io/v2/template/pdf", { method: "POST", headers: { "X-API-Key": "your-api-key-here", // Do not set Content-Type — fetch sets it automatically with the boundary }, body: formData, }); const job = await response.json(); ``` **Python (requests)** ```python import json import requests with open("my-template.html", "rb") as f: template_file = f.read() response = requests.post( "https://api.cloudlayer.io/v2/template/pdf", headers={"X-API-Key": "your-api-key-here"}, files={"template": ("template.html", template_file, "text/html")}, data={"data": json.dumps({"invoiceNumber": "INV-001", "total": 450.00})}, ) job = response.json() ``` --- ## Template Syntax Templates use [Nunjucks](https://mozilla.github.io/nunjucks/) syntax for data binding. Here are the most common patterns: ### Variable Output ```html

Hello, {{name}}!

{{ htmlContent | safe }}
``` ### Conditionals ```html {% if isPaid %} {% else %} Unpaid {% endif %} ``` ### Loops ```html {% for item in items %} {% endfor %}
ItemQtyPrice
{{item.name}} {{item.quantity}} ${{item.price}}
``` ### Nested Data ```html

{{company.name}}

{{company.address.street}}

{{company.address.city}}, {{company.address.state}} {{company.address.zip}}

``` --- ## Full Example: Invoice with Custom Template **JavaScript (fetch)** ```javascript const template = `
{{companyName}}
{{invoiceNumber}}
Date: {{date}}
Due: {{dueDate}}
Bill To:
{{customerName}}
{{customerEmail}}
{% for item in items %} {% endfor %}
ItemQuantityUnit PriceTotal
{{item.name}} {{item.quantity}} \${{item.price}} \${{item.total}}
Total \${{grandTotal}}
{% if notes %}
Notes: {{notes}}
{% endif %} `; const response = await fetch("https://api.cloudlayer.io/v2/template/pdf", { method: "POST", headers: { "X-API-Key": "your-api-key-here", "Content-Type": "application/json", }, body: JSON.stringify({ template: btoa(template), data: { companyName: "Acme Corp", invoiceNumber: "INV-2024-042", date: "2024-01-15", dueDate: "2024-02-14", customerName: "Jane Smith", customerEmail: "jane@example.com", items: [ { name: "Web Development", quantity: 40, price: "150.00", total: "6,000.00" }, { name: "Design Services", quantity: 20, price: "125.00", total: "2,500.00" }, { name: "Hosting (Annual)", quantity: 1, price: "299.00", total: "299.00" }, ], grandTotal: "8,799.00", notes: "Please include the invoice number in your payment reference.", }, format: "letter", margin: { top: "0.5in", bottom: "0.5in", left: "0.5in", right: "0.5in" }, printBackground: true, filename: "invoice-2024-042.pdf", }), }); const job = await response.json(); ``` --- ## Tips - **Nunjucks `| safe` filter:** Use `{{ variable | safe }}` when your data contains HTML that should be rendered, not escaped. Use `{{variable}}` for plain text to prevent XSS. - **External resources:** Custom templates can reference external CSS, fonts, and images via URLs. Make sure they are publicly accessible. - **Template reuse:** If you use the same template repeatedly, consider adding it to the template gallery via the dashboard and referencing it by `templateId`. This reduces payload size and simplifies your API calls. - **Debugging templates:** Test your Nunjucks template locally using the [Nunjucks documentation and playground](https://mozilla.github.io/nunjucks/) before sending it to the API. - **Multipart vs. JSON:** Use multipart uploads when your template file is large or when you want to avoid base64 encoding overhead. ### HTML to Image Source: https://cloudlayer.io/docs/html-to-image/ # HTML to Image Generate an image (PNG, JPG, or WebP) from raw HTML content. The HTML is sent as a base64-encoded string in the request body. ## Endpoint
``` POST /v1/html/image ``` All requests are processed synchronously. The response body is the raw image file.
``` POST /v2/html/image ``` Requests default to **asynchronous** processing (`async: true`, `storage: true`). Add `"async": false` to receive the raw image directly. See [Sync vs. Async](/docs/api-overview/#synchronous-vs-asynchronous-requests) for details.
--- ## Quick Start
**cURL** ```bash HTML_BASE64=$(echo '

Hello World

Generated by cloudlayer.io

' | base64) curl -X POST "https://api.cloudlayer.io/v1/html/image" \ -H "X-API-Key: your-api-key-here" \ -H "Content-Type: application/json" \ -d "{\"html\": \"$HTML_BASE64\"}" \ --output result.png ``` **JavaScript (fetch)** ```javascript const html = `

Hello World

Generated by cloudlayer.io

`; const response = await fetch("https://api.cloudlayer.io/v1/html/image", { method: "POST", headers: { "X-API-Key": "your-api-key-here", "Content-Type": "application/json", }, body: JSON.stringify({ html: btoa(html), }), }); const image = await response.arrayBuffer(); ``` **Python (requests)** ```python import base64 import requests html = """

Hello World

Generated by cloudlayer.io

""" response = requests.post( "https://api.cloudlayer.io/v1/html/image", headers={ "X-API-Key": "your-api-key-here", "Content-Type": "application/json", }, json={ "html": base64.b64encode(html.encode()).decode(), }, ) with open("result.png", "wb") as f: f.write(response.content) ```
**cURL** ```bash HTML_BASE64=$(echo '

Hello World

Generated by cloudlayer.io

' | base64) curl -X POST "https://api.cloudlayer.io/v2/html/image" \ -H "X-API-Key: your-api-key-here" \ -H "Content-Type: application/json" \ -d "{\"html\": \"$HTML_BASE64\"}" ``` Response: ```json { "id": "abc123", "status": "pending" } ``` **JavaScript (fetch)** ```javascript const html = `

Hello World

Generated by cloudlayer.io

`; const response = await fetch("https://api.cloudlayer.io/v2/html/image", { method: "POST", headers: { "X-API-Key": "your-api-key-here", "Content-Type": "application/json", }, body: JSON.stringify({ html: btoa(html), }), }); const { id, status } = await response.json(); // Poll job status or use webhooks to know when the image is ready ``` **Python (requests)** ```python import base64 import requests html = """

Hello World

Generated by cloudlayer.io

""" response = requests.post( "https://api.cloudlayer.io/v2/html/image", headers={ "X-API-Key": "your-api-key-here", "Content-Type": "application/json", }, json={ "html": base64.b64encode(html.encode()).decode(), }, ) job = response.json() # Poll job status or use webhooks to know when the image is ready ``` > **Tip:** v2 always returns JSON. Use the job `id` to poll the [Jobs](/docs/jobs/) endpoint or configure [Webhooks](/docs/webhooks/) for completion notifications. The generated file is accessible via the [Assets](/docs/assets/) endpoint. Add `"async": false` to wait for processing to complete — the response is still JSON but includes the completed job details.
--- ## Parameters ### Required | Parameter | Type | Description | | --- | --- | --- | | `html` | string | **Required.** Base64-encoded HTML content. The HTML can include inline CSS, `

${title}

${subtitle}

`; const response = await fetch("https://api.cloudlayer.io/v2/html/image", { method: "POST", headers: { "X-API-Key": "your-api-key-here", "Content-Type": "application/json", }, body: JSON.stringify({ html: btoa(html), imageType: "png", viewPort: { width: 1200, height: 630, }, trim: false, }), }); const job = await response.json(); ``` ### Trimmed Element Screenshot Capture just the content with no extra whitespace: ```json { "html": "...", "imageType": "png", "trim": true, "viewPort": { "width": 800, "height": 600 } } ``` --- ## Tips - **Image dimensions:** The output image size is determined by the viewport and content dimensions. Use `viewPort` to control the rendering area and `width`/`height` to set explicit capture dimensions. - **Retina quality:** Set `scale: 2` or `viewPort.deviceScaleFactor: 2` for crisp images on high-DPI displays. This doubles the pixel dimensions. - **File size:** Use `"jpg"` for photographs and complex images (smaller file size). Use `"png"` for graphics with text, logos, or transparency. Use `"webp"` for the best compression-to-quality ratio. - **Transparency:** Only works with `"png"` and `"webp"`. Remove all background colors from your HTML/CSS. - **Dynamic content:** Use `waitUntil: "networkidle0"` and `delay` for pages that load charts, maps, or other dynamic content. ### URL to Image Source: https://cloudlayer.io/docs/url-to-image/ # URL to Image Capture a screenshot of any web page as a PNG, JPG, or WebP image. Supports both simple GET requests and full-featured POST requests with authentication and cookies. ## Endpoints The **GET** endpoint is a convenience method for simple screenshots. The **POST** endpoint supports the full range of parameters.
``` GET /v1/url/image POST /v1/url/image ``` All v1 requests are processed synchronously. The response body is the raw image file.
``` GET /v2/url/image POST /v2/url/image ``` v2 requests default to **asynchronous** processing (`async: true`, `storage: true`). Add `"async": false` to receive the raw image directly. See [Sync vs. Async](/docs/api-overview/#synchronous-vs-asynchronous-requests) for details.
--- ## Quick Start (GET) Capture a web page screenshot with a single GET request:
**cURL** ```bash curl -X GET "https://api.cloudlayer.io/v1/url/image?url=https://example.com" \ -H "X-API-Key: your-api-key-here" \ --output screenshot.png ``` **JavaScript (fetch)** ```javascript const url = encodeURIComponent("https://example.com"); const response = await fetch( `https://api.cloudlayer.io/v1/url/image?url=${url}`, { headers: { "X-API-Key": "your-api-key-here", }, } ); const image = await response.arrayBuffer(); ``` **Python (requests)** ```python import requests response = requests.get( "https://api.cloudlayer.io/v1/url/image", params={"url": "https://example.com"}, headers={"X-API-Key": "your-api-key-here"}, ) with open("screenshot.png", "wb") as f: f.write(response.content) ```
**cURL** ```bash curl -X GET "https://api.cloudlayer.io/v2/url/image?url=https://example.com" \ -H "X-API-Key: your-api-key-here" ``` Response: ```json { "id": "abc123", "status": "pending" } ``` **JavaScript (fetch)** ```javascript const url = encodeURIComponent("https://example.com"); const response = await fetch( `https://api.cloudlayer.io/v2/url/image?url=${url}`, { headers: { "X-API-Key": "your-api-key-here", }, } ); const job = await response.json(); ``` **Python (requests)** ```python import requests response = requests.get( "https://api.cloudlayer.io/v2/url/image", params={"url": "https://example.com"}, headers={"X-API-Key": "your-api-key-here"}, ) job = response.json() ```
--- ## Quick Start (POST) Use POST for full control over the screenshot:
**cURL** ```bash curl -X POST "https://api.cloudlayer.io/v1/url/image" \ -H "X-API-Key: your-api-key-here" \ -H "Content-Type: application/json" \ -d '{ "url": "https://example.com", "imageType": "webp", "viewPort": { "width": 1920, "height": 1080, "deviceScaleFactor": 2 }, "waitUntil": "networkidle0" }' \ --output screenshot.webp ``` **JavaScript (fetch)** ```javascript const response = await fetch("https://api.cloudlayer.io/v1/url/image", { method: "POST", headers: { "X-API-Key": "your-api-key-here", "Content-Type": "application/json", }, body: JSON.stringify({ url: "https://example.com", imageType: "webp", viewPort: { width: 1920, height: 1080, deviceScaleFactor: 2, }, waitUntil: "networkidle0", }), }); const image = await response.arrayBuffer(); ``` **Python (requests)** ```python import requests response = requests.post( "https://api.cloudlayer.io/v1/url/image", headers={ "X-API-Key": "your-api-key-here", "Content-Type": "application/json", }, json={ "url": "https://example.com", "imageType": "webp", "viewPort": { "width": 1920, "height": 1080, "deviceScaleFactor": 2, }, "waitUntil": "networkidle0", }, ) with open("screenshot.webp", "wb") as f: f.write(response.content) ```
**cURL** ```bash curl -X POST "https://api.cloudlayer.io/v2/url/image" \ -H "X-API-Key: your-api-key-here" \ -H "Content-Type: application/json" \ -d '{ "url": "https://example.com", "imageType": "webp", "viewPort": { "width": 1920, "height": 1080, "deviceScaleFactor": 2 }, "waitUntil": "networkidle0" }' ``` **JavaScript (fetch)** ```javascript const response = await fetch("https://api.cloudlayer.io/v2/url/image", { method: "POST", headers: { "X-API-Key": "your-api-key-here", "Content-Type": "application/json", }, body: JSON.stringify({ url: "https://example.com", imageType: "webp", viewPort: { width: 1920, height: 1080, deviceScaleFactor: 2, }, waitUntil: "networkidle0", }), }); const job = await response.json(); ``` **Python (requests)** ```python import requests response = requests.post( "https://api.cloudlayer.io/v2/url/image", headers={ "X-API-Key": "your-api-key-here", "Content-Type": "application/json", }, json={ "url": "https://example.com", "imageType": "webp", "viewPort": { "width": 1920, "height": 1080, "deviceScaleFactor": 2, }, "waitUntil": "networkidle0", }, ) job = response.json() ``` > **Tip:** v2 always returns JSON. Use the job `id` to poll the [Jobs](/docs/jobs/) endpoint or configure [Webhooks](/docs/webhooks/) for completion notifications. The generated file is accessible via the [Assets](/docs/assets/) endpoint. Add `"async": false` to wait for processing to complete — the response is still JSON but includes the completed job details.
--- ## GET Parameters | Parameter | Type | Required | Default | Description | | --- | --- | --- | --- | --- | | `url` | string | Yes | — | The URL of the web page to capture. Must be URL-encoded. | | `timeout` | number | No | `30000` | Maximum time in milliseconds to wait for the page to load. | --- ## POST Parameters ### URL Parameters | Parameter | Type | Required | Default | Description | | --- | --- | --- | --- | --- | | `url` | string | Yes | — | The URL of the web page to capture. | | `authentication` | object | No | — | Credentials for HTTP Basic Authentication. See [URL to PDF — authentication](/docs/url-to-pdf/#authentication-object). | | `cookies` | array | No | — | Cookies to set before navigating. See [URL to PDF — cookies](/docs/url-to-pdf/#cookies-array). | ### Image Parameters | Parameter | Type | Default | Description | | --- | --- | --- | --- | | `imageType` | string | `"png"` | Output image format: `"png"`, `"jpg"`, `"jpeg"`, `"webp"`, or `"svg"`. | | `quality` | number | — | Image quality (1-100). Only applies to `jpg`, `jpeg`, and `webp` formats. | | `transparent` | boolean | `false` | Render with a transparent background. Only works with `"png"` and `"webp"`. The target page must not set a background color. | | `trim` | boolean | `false` | Trim whitespace from all edges of the resulting image. | ### Base Parameters | Parameter | Type | Default | Description | | --- | --- | --- | --- | | `autoScroll` | boolean | `false` | Scroll the page to the bottom before capturing. Triggers lazy-loaded content and infinite scroll. | | `delay` | number | `0` | Wait time in milliseconds after the page loads before capturing. | | `filename` | string | — | Set the `Content-Disposition` filename on the response. | | `height` | string | — | Set the capture area height with CSS units. | | `width` | string | — | Set the capture area width with CSS units. | | `inline` | boolean | `false` | Display the image inline in the browser instead of downloading. | | `landscape` | boolean | `false` | Render in landscape orientation. | | `preferCSSPageSize` | boolean | `false` | Use CSS `@page` size for dimensions. | | `projectId` | string | — | Associate with a project for dashboard organization. | | `scale` | number | `1` | Page rendering scale (`0.1` to `2`). | | `timeout` | number | `30000` | Maximum time in milliseconds to wait for page load. | | `timeZone` | string | — | Browser timezone (IANA name). | | `viewPort` | object | — | Browser viewport configuration. See [HTML to PDF — viewPort](/docs/html-to-pdf/#viewport-object). | | `waitForSelector` | object | — | Wait for a CSS selector before capturing. See [HTML to PDF — waitForSelector](/docs/html-to-pdf/#waitforselector-object). | | `waitUntil` | string | `"networkidle2"` | Navigation completion strategy: `"load"`, `"domcontentloaded"`, `"networkidle0"`, `"networkidle2"`. | --- ## Examples ### Mobile Screenshot Capture the mobile version of a responsive website: ```json { "url": "https://example.com", "imageType": "png", "viewPort": { "width": 375, "height": 812, "deviceScaleFactor": 3, "isMobile": true, "hasTouch": true }, "waitUntil": "networkidle0" } ``` ### Full-Page Screenshot with Auto-Scroll Capture the entire page, including below-the-fold content and lazy-loaded images: ```json { "url": "https://example.com/blog/long-article", "imageType": "jpg", "autoScroll": true, "waitUntil": "networkidle0", "delay": 1000 } ``` ### Authenticated Dashboard Screenshot Capture a dashboard behind cookie-based authentication: **cURL** ```bash curl -X POST "https://api.cloudlayer.io/v2/url/image" \ -H "X-API-Key: your-api-key-here" \ -H "Content-Type: application/json" \ -d '{ "url": "https://app.example.com/dashboard", "cookies": [ { "name": "session_token", "value": "abc123...", "domain": "app.example.com", "secure": true, "httpOnly": true } ], "imageType": "png", "viewPort": { "width": 1440, "height": 900 }, "waitUntil": "networkidle0", "delay": 2000, "filename": "dashboard-screenshot.png" }' ``` ### High-Resolution Screenshot for Print Generate a high-DPI screenshot suitable for print materials: ```json { "url": "https://example.com", "imageType": "png", "scale": 2, "viewPort": { "width": 1200, "height": 800, "deviceScaleFactor": 2 }, "waitUntil": "networkidle0" } ``` ### Trimmed Element Capture Capture just the rendered content with whitespace removed: ```json { "url": "https://example.com/widget", "imageType": "png", "trim": true, "transparent": true, "viewPort": { "width": 600, "height": 400 } } ``` --- ## Tips - **Full-page screenshots:** Use `autoScroll: true` to load all lazy content, then the renderer captures the full scrollable area. - **Dynamic content:** For SPAs and pages with charts/graphs, use `waitUntil: "networkidle0"` with a `delay` of 1-3 seconds to ensure all data has loaded and rendered. - **File format choice:** Use PNG for screenshots with text and sharp edges. Use JPG for photograph-heavy pages (smaller file size). Use WebP for the best balance of quality and compression. - **Viewport matters:** The default viewport is 1440x900. Set the viewport explicitly to control exactly what is captured, especially for responsive sites. - **Transparent screenshots:** Only works with PNG and WebP. The target page must have no background color set on `` or ``. Many sites set a white background, so transparency works best with your own HTML content via the [HTML to Image](/docs/html-to-image/) endpoint. ### Template to Image Source: https://cloudlayer.io/docs/template-to-image/ # Template to Image Generate an image (PNG, JPG, or WebP) from a template with dynamic data. Use predefined templates from the cloudlayer.io gallery or provide your own custom HTML template with Nunjucks syntax. ## Endpoint
``` POST /v1/template/image ``` All requests are processed synchronously. The response body is the raw image file.
``` POST /v2/template/image ``` Requests default to **asynchronous** processing (`async: true`, `storage: true`). Add `"async": false` to receive the raw image directly. See [Sync vs. Async](/docs/api-overview/#synchronous-vs-asynchronous-requests) for details.
Supports two content types: | Content Type | Use Case | | --- | --- | | `application/json` | Inline templates (base64-encoded) or predefined template IDs with JSON data. | | `multipart/form-data` | Upload template files directly alongside JSON data. | --- ## Quick Start: Predefined Template
**cURL** ```bash curl -X POST "https://api.cloudlayer.io/v1/template/image" \ -H "X-API-Key: your-api-key-here" \ -H "Content-Type: application/json" \ -d '{ "templateId": "social-card", "data": { "title": "Introducing Our New API", "subtitle": "Generate images programmatically", "author": "cloudlayer.io" }, "imageType": "png" }' \ --output social-card.png ``` **JavaScript (fetch)** ```javascript const response = await fetch("https://api.cloudlayer.io/v1/template/image", { method: "POST", headers: { "X-API-Key": "your-api-key-here", "Content-Type": "application/json", }, body: JSON.stringify({ templateId: "social-card", data: { title: "Introducing Our New API", subtitle: "Generate images programmatically", author: "cloudlayer.io", }, imageType: "png", }), }); const image = await response.arrayBuffer(); ``` **Python (requests)** ```python import requests response = requests.post( "https://api.cloudlayer.io/v1/template/image", headers={ "X-API-Key": "your-api-key-here", "Content-Type": "application/json", }, json={ "templateId": "social-card", "data": { "title": "Introducing Our New API", "subtitle": "Generate images programmatically", "author": "cloudlayer.io", }, "imageType": "png", }, ) with open("social-card.png", "wb") as f: f.write(response.content) ```
**cURL** ```bash curl -X POST "https://api.cloudlayer.io/v2/template/image" \ -H "X-API-Key: your-api-key-here" \ -H "Content-Type: application/json" \ -d '{ "templateId": "social-card", "data": { "title": "Introducing Our New API", "subtitle": "Generate images programmatically", "author": "cloudlayer.io" }, "imageType": "png" }' ``` **JavaScript (fetch)** ```javascript const response = await fetch("https://api.cloudlayer.io/v2/template/image", { method: "POST", headers: { "X-API-Key": "your-api-key-here", "Content-Type": "application/json", }, body: JSON.stringify({ templateId: "social-card", data: { title: "Introducing Our New API", subtitle: "Generate images programmatically", author: "cloudlayer.io", }, imageType: "png", }), }); const job = await response.json(); ``` **Python (requests)** ```python import requests response = requests.post( "https://api.cloudlayer.io/v2/template/image", headers={ "X-API-Key": "your-api-key-here", "Content-Type": "application/json", }, json={ "templateId": "social-card", "data": { "title": "Introducing Our New API", "subtitle": "Generate images programmatically", "author": "cloudlayer.io", }, "imageType": "png", }, ) job = response.json() ``` > **Tip:** v2 always returns JSON. Use the job `id` to poll the [Jobs](/docs/jobs/) endpoint or configure [Webhooks](/docs/webhooks/) for completion notifications. The generated file is accessible via the [Assets](/docs/assets/) endpoint. Add `"async": false` to wait for processing to complete — the response is still JSON but includes the completed job details.
--- ## Quick Start: Custom Template **cURL** ```bash TEMPLATE=$(cat <<'HTML'

{{title}}

{{description}}

HTML ) TEMPLATE_BASE64=$(echo "$TEMPLATE" | base64 -w 0) curl -X POST "https://api.cloudlayer.io/v2/template/image" \ -H "X-API-Key: your-api-key-here" \ -H "Content-Type: application/json" \ -d "{ \"template\": \"$TEMPLATE_BASE64\", \"data\": { \"title\": \"Hello World\", \"description\": \"Generated with cloudlayer.io\" }, \"imageType\": \"png\" }" ``` **JavaScript (fetch)** ```javascript const template = `

{{title}}

{{description}}

`; const response = await fetch("https://api.cloudlayer.io/v2/template/image", { method: "POST", headers: { "X-API-Key": "your-api-key-here", "Content-Type": "application/json", }, body: JSON.stringify({ template: btoa(template), data: { title: "Hello World", description: "Generated with cloudlayer.io", }, imageType: "png", }), }); const job = await response.json(); ``` **Python (requests)** ```python import base64 import requests template = """

{{title}}

{{description}}

""" response = requests.post( "https://api.cloudlayer.io/v2/template/image", headers={ "X-API-Key": "your-api-key-here", "Content-Type": "application/json", }, json={ "template": base64.b64encode(template.encode()).decode(), "data": { "title": "Hello World", "description": "Generated with cloudlayer.io", }, "imageType": "png", }, ) job = response.json() ``` --- ## Parameters ### Template Parameters You must provide either `templateId` or `template`, but not both. | Parameter | Type | Required | Default | Description | | --- | --- | --- | --- | --- | | `templateId` | string | Conditional | — | ID of a predefined template from the cloudlayer.io template gallery. | | `template` | string | Conditional | — | Custom HTML template as a base64-encoded string. Supports Nunjucks syntax. | | `data` | object | No | `{}` | Key-value pairs of data to inject into the template. Keys map to Nunjucks placeholders (e.g., `{{name}}`). | ### Image Parameters | Parameter | Type | Default | Description | | --- | --- | --- | --- | | `imageType` | string | `"png"` | Output format: `"png"`, `"jpg"`, `"jpeg"`, `"webp"`, or `"svg"`. | | `quality` | number | — | Image quality (1-100). Only applies to `jpg`, `jpeg`, and `webp` formats. | | `transparent` | boolean | `false` | Render with a transparent background. Only works with `"png"` and `"webp"`. The template must not set a background color on `` or ``. | | `trim` | boolean | `false` | Trim whitespace from all edges of the resulting image. | ### Base Parameters | Parameter | Type | Default | Description | | --- | --- | --- | --- | | `autoScroll` | boolean | `false` | Scroll the page to trigger lazy-loaded content before capture. | | `delay` | number | `0` | Wait time in milliseconds after the page loads before capturing. | | `filename` | string | — | Set the `Content-Disposition` filename on the response. | | `height` | string | — | Set the capture area height with CSS units. | | `width` | string | — | Set the capture area width with CSS units. | | `inline` | boolean | `false` | Display the image inline in the browser instead of downloading. | | `landscape` | boolean | `false` | Render in landscape orientation. | | `preferCSSPageSize` | boolean | `false` | Use CSS `@page` size for dimensions. | | `projectId` | string | — | Associate with a project for dashboard organization. | | `scale` | number | `1` | Page rendering scale (`0.1` to `2`). | | `timeout` | number | `30000` | Maximum page load time in milliseconds. | | `timeZone` | string | — | Browser timezone (IANA name). | | `viewPort` | object | — | Browser viewport configuration. See [HTML to PDF — viewPort](/docs/html-to-pdf/#viewport-object). | | `waitForSelector` | object | — | Wait for a CSS selector before capturing. See [HTML to PDF — waitForSelector](/docs/html-to-pdf/#waitforselector-object). | | `waitUntil` | string | `"networkidle2"` | Navigation completion strategy. | --- ## Use Cases ### Open Graph (OG) Images Generate dynamic social sharing images for blog posts, product pages, and marketing campaigns: ```javascript const response = await fetch("https://api.cloudlayer.io/v2/template/image", { method: "POST", headers: { "X-API-Key": "your-api-key-here", "Content-Type": "application/json", }, body: JSON.stringify({ templateId: "og-image-blog", data: { title: "10 Tips for Better API Design", author: "Jane Smith", authorAvatar: "https://example.com/avatars/jane.jpg", readTime: "5 min read", category: "Engineering", }, imageType: "png", viewPort: { width: 1200, height: 630 }, }), }); ``` ### Email Banners Generate personalized email header images: ```json { "templateId": "email-banner", "data": { "recipientName": "Jane", "promoCode": "SAVE20", "expiryDate": "March 31, 2024" }, "imageType": "png", "viewPort": { "width": 600, "height": 200 } } ``` ### Certificate Generation Generate certificates with dynamic recipient data: ```json { "templateId": "certificate-completion", "data": { "recipientName": "Jane Smith", "courseName": "Advanced JavaScript", "completionDate": "January 15, 2024", "instructorName": "John Doe", "certificateId": "CERT-2024-0042" }, "imageType": "png", "viewPort": { "width": 1100, "height": 850 }, "scale": 2 } ``` ### Product Labels and Badges Generate dynamic product labels: ```json { "template": "...", "data": { "productName": "Organic Green Tea", "weight": "250g", "price": "$12.99", "barcode": "1234567890" }, "imageType": "png", "trim": true, "transparent": true } ``` --- ## Multipart Upload Upload template files directly instead of base64-encoding: **cURL** ```bash curl -X POST "https://api.cloudlayer.io/v2/template/image" \ -H "X-API-Key: your-api-key-here" \ -F "template=@./social-card-template.html" \ -F 'data={"title":"Hello World","description":"Dynamic image generation"}' ``` **Python (requests)** ```python import json import requests with open("social-card-template.html", "rb") as f: template_file = f.read() response = requests.post( "https://api.cloudlayer.io/v2/template/image", headers={"X-API-Key": "your-api-key-here"}, files={"template": ("template.html", template_file, "text/html")}, data={ "data": json.dumps( {"title": "Hello World", "description": "Dynamic image generation"} ), }, ) job = response.json() ``` --- ## Tips - **Fixed dimensions:** For consistent image output (e.g., OG images), set explicit dimensions in both the template CSS (`width`/`height` on ``) and the `viewPort` parameter. - **Template syntax:** Templates use [Nunjucks](https://mozilla.github.io/nunjucks/) syntax. See [Template to PDF — Template Syntax](/docs/template-to-pdf/#template-syntax) for conditionals, loops, and more. - **High-DPI images:** Set `scale: 2` or `viewPort.deviceScaleFactor: 2` for images that look crisp on retina displays. - **Transparent images:** Use `"png"` or `"webp"` with `transparent: true`. Do not set any background color in your template CSS. - **Batch generation:** To generate many images from the same template with different data, make parallel API calls. Consider using async mode for large batches to avoid timeout issues. ### Account Source: https://cloudlayer.io/docs/account/ # Account Retrieve information about your cloudlayer.io account, including API call counts, subscription details, storage usage, compute time, and credit balance. ## Endpoint
``` GET /v1/account ```
``` GET /v2/account ```
--- ## Request No parameters required. Authentication is via the `X-API-Key` header. **cURL** ```bash curl -X GET "https://api.cloudlayer.io/v2/account" \ -H "X-API-Key: your-api-key-here" ``` **JavaScript (fetch)** ```javascript const response = await fetch("https://api.cloudlayer.io/v2/account", { headers: { "X-API-Key": "your-api-key-here", }, }); const account = await response.json(); console.log(account); ``` **Python (requests)** ```python import requests response = requests.get( "https://api.cloudlayer.io/v2/account", headers={"X-API-Key": "your-api-key-here"}, ) account = response.json() print(account) ``` --- ## Response The response shape varies slightly depending on your billing model. See [Billing Models](#billing-models) below. **Subscription (limit-based) example:** ```json { "email": "user@example.com", "uid": "aBcDeFgHiJkLmNoPqRsTuVwXyZ12", "subscription": "price-starter-1k", "subType": "limit", "subActive": true, "calls": 842, "callsLimit": 1000, "bytesTotal": 1073741824, "bytesLimit": 5368709120, "computeTimeTotal": 384200, "computeTimeLimit": 600000, "storageUsed": 52428800, "storageLimit": 1073741824, "totalJobs": 900, "successJobs": 858, "errorJobs": 42 } ``` **Usage (credit-based) example:** ```json { "email": "user@example.com", "uid": "aBcDeFgHiJkLmNoPqRsTuVwXyZ12", "subscription": "price-growth-usage", "subType": "usage", "subActive": true, "calls": 15234, "callsLimit": -1, "credit": 4500, "bytesTotal": 2147483648, "bytesLimit": -1, "computeTimeTotal": 720000, "computeTimeLimit": -1, "storageUsed": 104857600, "storageLimit": -1, "totalJobs": 15300, "successJobs": 15234, "errorJobs": 66 } ``` ## Response Fields | Field | Type | Description | | --- | --- | --- | | `email` | string | The email address associated with the account. | | `uid` | string | The unique user ID for the account. | | `subscription` | string | The price ID of your current subscription (e.g., `"price-starter-1k"`). | | `subType` | string | Billing model: `"limit"` (subscription call limits) or `"usage"` (credit-based pay-per-use). See [Billing Models](#billing-models). | | `subActive` | boolean | Whether the subscription is currently active. | | `calls` | number | Total number of API calls made in the current billing period. | | `callsLimit` | number | Maximum API calls allowed per billing period. `-1` indicates unlimited (usage-based plans). | | `credit` | number | Remaining API credits. **Only present for usage-based plans** (`subType: "usage"`). Each API call consumes one or more credits depending on the operation. | | `bytesTotal` | number | Total output bytes generated across all jobs. Divide by `1048576` for megabytes or `1073741824` for gigabytes. | | `bytesLimit` | number | Maximum output bytes allowed per billing period. `-1` indicates unlimited. | | `computeTimeTotal` | number | Total compute time used in milliseconds across all jobs in the current billing period. | | `computeTimeLimit` | number | Maximum compute time allowed in milliseconds per billing period. `-1` indicates unlimited. | | `storageUsed` | number | Total cloud storage currently used in bytes for stored assets. | | `storageLimit` | number | Maximum cloud storage allowed in bytes. `-1` indicates unlimited. | | `totalJobs` | number | Total number of completed jobs (success + error). | | `successJobs` | number | Number of successfully completed jobs. | | `errorJobs` | number | Number of failed jobs. | --- ## Billing Models cloudlayer.io offers two billing models. The `subType` field in the account response indicates which model your plan uses. ### Limit-Based (`subType: "limit"`) Subscription plans with a fixed number of API calls per billing period. The `calls` field tracks usage against `callsLimit`. When `calls` reaches `callsLimit`, further API requests are rejected until the next billing period. - The `credit` field is **not present** in the response. - `callsLimit`, `bytesLimit`, `computeTimeLimit`, and `storageLimit` reflect your plan's caps. ### Usage-Based (`subType: "usage"`) Credit-based plans where each API call deducts from your `credit` balance. There is no fixed call limit per billing period. - The `credit` field **is present** in the response, showing your remaining balance. - Limit fields (`callsLimit`, `bytesLimit`, etc.) are typically `-1` (unlimited). --- ## Use Cases ### Monitor Usage Check your API usage to stay within plan limits: ```javascript const response = await fetch("https://api.cloudlayer.io/v2/account", { headers: { "X-API-Key": "your-api-key-here" }, }); const account = await response.json(); console.log(`Calls this period: ${account.calls}`); console.log(`Storage used: ${(account.storageUsed / 1073741824).toFixed(2)} GB`); console.log(`Jobs completed: ${account.successJobs}`); console.log(`Jobs failed: ${account.errorJobs}`); if (account.subType === "limit") { console.log(`Calls remaining: ${account.callsLimit - account.calls}`); } else { console.log(`Credit balance: ${account.credit}`); } ``` ### Usage Alerts Set up an automated check to alert you when usage is running low: ```python import requests response = requests.get( "https://api.cloudlayer.io/v2/account", headers={"X-API-Key": "your-api-key-here"}, ) account = response.json() if account["subType"] == "usage" and account.get("credit", 0) < 100: print(f"WARNING: Only {account['credit']} credits remaining!") # Send alert via email, Slack, etc. if account["subType"] == "limit" and account["callsLimit"] > 0: usage_pct = account["calls"] / account["callsLimit"] * 100 if usage_pct > 90: print(f"WARNING: {usage_pct:.0f}% of call limit used!") ``` --- ## Tips - **Billing period:** The `calls` count resets at the beginning of each billing period. Check your dashboard for your billing cycle dates. - **Credit consumption:** Different operations consume different amounts of credits. PDF generation with large HTML or many pages may consume more credits than a simple image capture. - **Storage management:** If `storageUsed` is approaching your `storageLimit`, use the [Assets](/docs/assets/) endpoint to review and clean up old generated files. ### Assets Source: https://cloudlayer.io/docs/assets/ # Assets Assets are the files generated by cloudlayer.io API calls, PDFs, PNGs, JPGs, and WebP images. Every document generation request produces an asset that you can retrieve later by its ID. Assets are particularly useful when using **async mode**, where the generation response returns a job ID instead of the file directly. Once the job completes, the asset is available for download. ## Endpoints
``` GET /v1/assets/:id GET /v1/assets ```
``` GET /v2/assets/:id GET /v2/assets ```
--- ## Get a Single Asset Retrieve a specific asset by its ID.
``` GET /v1/assets/:id ```
``` GET /v2/assets/:id ```
### Path Parameters | Parameter | Type | Required | Description | | --- | --- | --- | --- | | `id` | string | Yes | The unique asset ID. | ### Examples **cURL** ```bash curl -X GET "https://api.cloudlayer.io/v2/assets/aBcDeFgHiJk123" \ -H "X-API-Key: your-api-key-here" ``` **JavaScript (fetch)** ```javascript const assetId = "aBcDeFgHiJk123"; const response = await fetch( `https://api.cloudlayer.io/v2/assets/${assetId}`, { headers: { "X-API-Key": "your-api-key-here", }, } ); const asset = await response.json(); console.log(asset); ``` **Python (requests)** ```python import requests asset_id = "aBcDeFgHiJk123" response = requests.get( f"https://api.cloudlayer.io/v2/assets/{asset_id}", headers={"X-API-Key": "your-api-key-here"}, ) asset = response.json() print(asset) ``` ### Response ```json { "id": "aBcDeFgHiJk123", "jobId": "1705312200000", "ext": "pdf", "type": "application/pdf", "size": 245760, "url": "https://storage.cloudlayer.io/assets/aBcDeFgHiJk123.pdf", "timestamp": 1705312200000 } ``` --- ## List Assets Retrieve a list of your most recent assets, ordered by creation date (newest first). Results are limited to 10 items.
``` GET /v1/assets ```
``` GET /v2/assets ```
**Query Parameters:** | Parameter | Type | Default | Description | |-----------|------|---------|-------------| | `limit` | number | 10 | Number of assets to return (1–100) | | `startAfterId` | string | — | Asset ID to paginate after (for cursor-based pagination) | ### Examples **cURL** ```bash curl -X GET "https://api.cloudlayer.io/v2/assets" \ -H "X-API-Key: your-api-key-here" ``` **JavaScript (fetch)** ```javascript const response = await fetch( "https://api.cloudlayer.io/v2/assets", { headers: { "X-API-Key": "your-api-key-here", }, } ); const assets = await response.json(); console.log(assets); ``` **Python (requests)** ```python import requests response = requests.get( "https://api.cloudlayer.io/v2/assets", headers={"X-API-Key": "your-api-key-here"}, ) assets = response.json() for asset in assets: print(f"{asset['id']} - {asset['ext']} - {asset['size']} bytes") ``` ### Response ```json [ { "id": "aBcDeFgHiJk123", "jobId": "1705312200000", "ext": "pdf", "type": "application/pdf", "size": 245760, "url": "https://storage.cloudlayer.io/assets/aBcDeFgHiJk123.pdf", "timestamp": 1705312200000 }, { "id": "xYzAbCdEfGh456", "jobId": "1705311900000", "ext": "png", "type": "image/png", "size": 102400, "url": "https://storage.cloudlayer.io/assets/xYzAbCdEfGh456.png", "timestamp": 1705311900000 } ] ``` --- ## Response Fields | Field | Type | Description | | --- | --- | --- | | `id` | string | Unique asset identifier (e.g., `"aBcDeFgHiJk123"`). | | `jobId` | string | The ID of the job that produced this asset. Use this to correlate assets with their generation requests via the [Jobs](/docs/jobs/) endpoint. | | `ext` | string | File extension: `"pdf"`, `"png"`, `"jpg"`, or `"webp"`. | | `type` | string | MIME type of the asset (e.g., `"application/pdf"`, `"image/png"`, `"image/jpeg"`, `"image/webp"`). | | `size` | number | File size in bytes. | | `url` | string | Direct download URL for the asset. This URL is pre-authenticated and can be used to download the file without an API key. URL expiration depends on your storage configuration. | | `timestamp` | number | Unix epoch timestamp in milliseconds of when the asset was created. | --- ## Download an Asset The `url` field in the asset response is a direct download link. You can use it to download the file without additional authentication: **cURL** ```bash # First, get the asset metadata ASSET_URL=$(curl -s "https://api.cloudlayer.io/v2/assets/aBcDeFgHiJk123" \ -H "X-API-Key: your-api-key-here" | jq -r '.url') # Then download the file curl -o downloaded-file.pdf "$ASSET_URL" ``` **JavaScript (fetch)** ```javascript // Get asset metadata const metaResponse = await fetch( "https://api.cloudlayer.io/v2/assets/aBcDeFgHiJk123", { headers: { "X-API-Key": "your-api-key-here" }, } ); const asset = await metaResponse.json(); // Download the file using the pre-authenticated URL const fileResponse = await fetch(asset.url); const fileBuffer = await fileResponse.arrayBuffer(); ``` **Python (requests)** ```python import requests # Get asset metadata meta_response = requests.get( "https://api.cloudlayer.io/v2/assets/aBcDeFgHiJk123", headers={"X-API-Key": "your-api-key-here"}, ) asset = meta_response.json() # Download the file file_response = requests.get(asset["url"]) with open(f"downloaded.{asset['ext']}", "wb") as f: f.write(file_response.content) ``` --- ## Async Workflow When using async mode for document generation, use the Assets endpoint to retrieve the result: ```javascript // 1. Start an async generation job const jobResponse = await fetch("https://api.cloudlayer.io/v2/html/pdf", { method: "POST", headers: { "X-API-Key": "your-api-key-here", "Content-Type": "application/json", }, body: JSON.stringify({ html: btoa("

Hello World

"), async: true, }), }); const job = await jobResponse.json(); console.log(`Job started: ${job.id}`); // 2. Poll the job until it completes let jobStatus; do { await new Promise((resolve) => setTimeout(resolve, 1000)); // Wait 1 second const statusResponse = await fetch( `https://api.cloudlayer.io/v2/jobs/${job.id}`, { headers: { "X-API-Key": "your-api-key-here" } } ); jobStatus = await statusResponse.json(); } while (jobStatus.status === "pending"); // 3. Retrieve the generated asset if (jobStatus.status === "success") { const assetsResponse = await fetch( "https://api.cloudlayer.io/v2/assets", { headers: { "X-API-Key": "your-api-key-here" } } ); const assets = await assetsResponse.json(); console.log(`Download URL: ${assets[0].url}`); } ``` --- ## Tips - **Asset retention:** Assets are retained based on your subscription plan. Check your plan details for the retention period. Download and store important assets in your own storage if long-term retention is required. - **Storage usage:** Use the [Account](/docs/account/) endpoint to monitor your total storage usage (`storageUsed`). - **User storage:** Configure your own S3-compatible storage to have assets delivered directly to your bucket. See the [Storage](/docs/storage/) endpoint for configuration. - **Result limit:** The list endpoint returns the 10 most recent assets. ### Jobs Source: https://cloudlayer.io/docs/jobs/ # Jobs Every API call to cloudlayer.io creates a **job**. Jobs track the status, parameters, processing time, and cost of each generation request. Use the Jobs API to monitor async requests, debug failed generations, and audit API usage. ## Endpoints
``` GET /v1/jobs/:id GET /v1/jobs ```
``` GET /v2/jobs/:id GET /v2/jobs ```
--- ## Get a Single Job Retrieve a specific job by its ID.
``` GET /v1/jobs/:id ```
``` GET /v2/jobs/:id ```
### Path Parameters | Parameter | Type | Required | Description | | --------- | ------ | -------- | ------------------ | | `id` | string | Yes | The unique job ID. | ### Examples **cURL** ```bash curl -X GET "https://api.cloudlayer.io/v2/jobs/1705312200000" \ -H "X-API-Key: your-api-key-here" ``` **JavaScript (fetch)** ```javascript const jobId = '1705312200000'; const response = await fetch(`https://api.cloudlayer.io/v2/jobs/${jobId}`, { headers: { 'X-API-Key': 'your-api-key-here' } }); const job = await response.json(); console.log(job); ``` **Python (requests)** ```python import requests job_id = "1705312200000" response = requests.get( f"https://api.cloudlayer.io/v2/jobs/{job_id}", headers={"X-API-Key": "your-api-key-here"}, ) job = response.json() print(job) ``` ### Response ```json { "id": "1705312200000", "uid": "aBcDeFgHiJkLmNoPqRsTuVwXyZ12", "type": "html-pdf", "status": "success", "params": { "format": "letter", "margin": { "top": "1in", "bottom": "1in" }, "printBackground": true }, "size": 245760, "processTime": 2340, "apiCreditCost": 1, "workerName": "worker-us-east-1a", "timestamp": 1705312200000 } ``` --- ## List Jobs Retrieve a list of your most recent jobs, ordered by creation date (newest first). Results are limited to 10 items.
``` GET /v1/jobs ```
``` GET /v2/jobs ```
**Query Parameters:** | Parameter | Type | Default | Description | | -------------- | ------ | ------- | ------------------------------------------------------ | | `limit` | number | 10 | Number of jobs to return (1–100) | | `startAfterId` | string | — | Job ID to paginate after (for cursor-based pagination) | ### Examples **cURL** ```bash curl -X GET "https://api.cloudlayer.io/v2/jobs" \ -H "X-API-Key: your-api-key-here" ``` **JavaScript (fetch)** ```javascript const response = await fetch('https://api.cloudlayer.io/v2/jobs', { headers: { 'X-API-Key': 'your-api-key-here' } }); const jobs = await response.json(); for (const job of jobs) { console.log(`${job.id} | ${job.type} | ${job.status} | ${job.processTime}ms`); } ``` **Python (requests)** ```python import requests response = requests.get( "https://api.cloudlayer.io/v2/jobs", headers={"X-API-Key": "your-api-key-here"}, ) jobs = response.json() for job in jobs: print(f"{job['id']} | {job['type']} | {job['status']} | {job['processTime']}ms") ``` ### Response ```json [ { "id": "1705312200000", "uid": "aBcDeFgHiJkLmNoPqRsTuVwXyZ12", "type": "html-pdf", "status": "success", "params": { "format": "letter", "printBackground": true }, "size": 245760, "processTime": 2340, "apiCreditCost": 1, "workerName": "worker-us-east-1a", "timestamp": 1705312200000 }, { "id": "1705311900000", "uid": "aBcDeFgHiJkLmNoPqRsTuVwXyZ12", "type": "url-image", "status": "success", "params": { "url": "https://example.com", "imageType": "png" }, "size": 102400, "processTime": 3150, "apiCreditCost": 1, "workerName": "worker-us-east-1b", "timestamp": 1705311900000 } ] ``` --- ## Response Fields | Field | Type | Description | | --------------- | ------ | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `id` | string | Unique job identifier (e.g., `"1705312200000"`). | | `uid` | string | The user ID that owns this job. | | `type` | string | The job type, matching the API endpoint used. See [Job Types](#job-types) below. | | `status` | string | Current job status. See [Job Statuses](#job-statuses) below. | | `params` | object | The parameters that were sent with the API request (excluding the `html` or `template` body for privacy). Useful for debugging and understanding what configuration produced a given result. | | `size` | number | Size of the generated output file in bytes. `0` for pending or failed jobs. | | `processTime` | number | Time in milliseconds from when the job started processing to completion. `0` for pending jobs. | | `apiCreditCost` | number | Number of API credits consumed by this job. | | `workerName` | string | Identifier of the worker instance that processed this job. Useful for support and debugging. | | `timestamp` | number | Unix epoch timestamp in milliseconds of when the job was created. | ### Job Types | Type | Description | | ---------------- | ---------------------------- | | `html-pdf` | HTML to PDF generation | | `url-pdf` | URL to PDF generation | | `template-pdf` | Template to PDF generation | | `html-image` | HTML to image generation | | `url-image` | URL to image generation | | `template-image` | Template to image generation | ### Job Statuses | Status | Description | | --------- | ----------------------------------------------------------------------------- | | `pending` | The job has been created and is waiting to be processed. | | `success` | The job finished successfully. The generated asset is available for download. | | `error` | The job failed. Check the response for error details. | --- ## Polling for Async Jobs When using async mode, poll the job endpoint to check when processing is complete: **JavaScript (fetch)** ```javascript async function waitForJob(jobId, apiKey, maxWaitMs = 60000) { const startTime = Date.now(); const pollInterval = 1000; // 1 second while (Date.now() - startTime < maxWaitMs) { const response = await fetch(`https://api.cloudlayer.io/v2/jobs/${jobId}`, { headers: { 'X-API-Key': apiKey } }); const job = await response.json(); if (job.status === 'success') { return job; } if (job.status === 'error') { throw new Error(`Job ${jobId} failed`); } // Wait before polling again await new Promise((resolve) => setTimeout(resolve, pollInterval)); } throw new Error(`Job ${jobId} timed out after ${maxWaitMs}ms`); } // Usage const job = await waitForJob('1705312200000', 'your-api-key-here'); console.log(`Completed in ${job.processTime}ms, size: ${job.size} bytes`); ``` **Python (requests)** ```python import time import requests def wait_for_job(job_id, api_key, max_wait_seconds=60): start_time = time.time() poll_interval = 1 # seconds while time.time() - start_time < max_wait_seconds: response = requests.get( f"https://api.cloudlayer.io/v2/jobs/{job_id}", headers={"X-API-Key": api_key}, ) job = response.json() if job["status"] == "success": return job if job["status"] == "error": raise Exception(f"Job {job_id} failed") time.sleep(poll_interval) raise TimeoutError(f"Job {job_id} timed out after {max_wait_seconds}s") # Usage job = wait_for_job("1705312200000", "your-api-key-here") print(f"Completed in {job['processTime']}ms, size: {job['size']} bytes") ``` --- ## Usage Analytics Use the Jobs API to build your own usage dashboards and analytics: ```python import requests response = requests.get( "https://api.cloudlayer.io/v2/jobs", headers={"X-API-Key": "your-api-key-here"}, ) jobs = response.json() # Compute statistics total_credits = sum(job["apiCreditCost"] for job in jobs) avg_process_time = sum(job["processTime"] for job in jobs) / len(jobs) total_bytes = sum(job["size"] for job in jobs) failed_count = sum(1 for job in jobs if job["status"] == "error") print(f"Total credits used: {total_credits}") print(f"Average processing time: {avg_process_time:.0f}ms") print(f"Total output size: {total_bytes / 1048576:.2f} MB") print(f"Failed jobs: {failed_count}") ``` --- ## Tips - **Debugging failures:** When a job fails, check the `params` field to see what configuration was used. Common issues include timeouts (increase the `timeout` parameter), unreachable URLs, and malformed HTML. - **Process time optimization:** Monitor `processTime` across jobs to identify slow generation patterns. Consider reducing viewport size, using `waitUntil: "domcontentloaded"`, or simplifying your HTML content. - **Result limit:** The list endpoint returns the 10 most recent jobs. ### Storage Source: https://cloudlayer.io/docs/storage/ # Storage cloudlayer.io supports three storage modes for generated files. You can use the built-in cloud storage (default), skip storage entirely, or connect your own S3-compatible bucket for direct delivery. ## Storage Types | Type | Description | Availability | | --- | --- | --- | | **No Storage** | Generated files are returned in the HTTP response only. No files are stored on cloudlayer.io servers. | All plans | | **Cloud Storage** | Generated files are stored on cloudlayer.io's managed cloud storage and accessible via the [Assets](/docs/assets/) API. This is the default in v2. | Included with all v2 plans | | **User Storage** | Generated files are delivered directly to your own S3-compatible storage bucket (AWS S3, Google Cloud Storage, DigitalOcean Spaces, MinIO, Backblaze B2, etc.). | Growth and Business plans | --- ## No Storage When you make a synchronous API request, the generated file is returned directly in the HTTP response body. v1 does not store it unless requested. In v2, set `"async": false` and `"storage": false` when you do not want the generated asset retained or available through the Assets API. For synchronous requests, the file is always included in the response regardless of storage settings. --- ## Cloud Storage (Default) Cloud storage is enabled by default for all v2 API requests. Generated files are automatically stored and accessible via the [Assets](/docs/assets/) endpoint. No configuration is needed. - Files are stored securely on cloudlayer.io infrastructure - Each file is accessible via a pre-authenticated download URL - Retention period depends on your subscription plan - Monitor usage via the [Account](/docs/account/) endpoint (`storageUsed`) --- ## User Storage (S3-Compatible) Connect your own S3-compatible storage bucket to have generated files delivered directly to your infrastructure. This gives you full control over file retention, access policies, and geographic placement. ### Endpoints Storage endpoints are available on both v1 and v2. ``` POST /v2/storage GET /v2/storage GET /v2/storage/:id DELETE /v2/storage/:id ``` --- ### Create or Update Storage Configuration Configure your S3-compatible storage bucket. ``` POST /v2/storage ``` #### Request Body | Field | Type | Required | Default | Description | | --- | --- | --- | --- | --- | | `title` | string | Yes | — | A unique name for this storage configuration (e.g., `"Production S3"`, `"Invoice Storage"`). Must be unique across your storage configurations. | | `bucket` | string | Yes | — | The S3 bucket name (e.g., `"my-company-pdfs"`). | | `region` | string | Yes | — | The AWS region or equivalent (e.g., `"us-east-1"`, `"eu-west-1"`). | | `accessKeyId` | string | Yes | — | The access key ID for your S3-compatible storage. | | `secretAccessKey` | string | Yes | — | The secret access key for your S3-compatible storage. | | `endpoint` | string | No | — | Custom endpoint URL for non-AWS S3-compatible services (e.g., `"https://nyc3.digitaloceanspaces.com"`, `"https://s3.eu-central-1.wasabisys.com"`). Omit for AWS S3. | #### Examples **cURL (AWS S3)** ```bash curl -X POST "https://api.cloudlayer.io/v2/storage" \ -H "X-API-Key: your-api-key-here" \ -H "Content-Type: application/json" \ -d '{ "title": "Production S3", "bucket": "my-company-pdfs", "region": "us-east-1", "accessKeyId": "AKIAIOSFODNN7EXAMPLE", "secretAccessKey": "wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY" }' ``` **cURL (DigitalOcean Spaces)** ```bash curl -X POST "https://api.cloudlayer.io/v2/storage" \ -H "X-API-Key: your-api-key-here" \ -H "Content-Type: application/json" \ -d '{ "title": "DO Spaces", "bucket": "my-space-name", "region": "nyc3", "accessKeyId": "your-spaces-access-key", "secretAccessKey": "your-spaces-secret-key", "endpoint": "https://nyc3.digitaloceanspaces.com" }' ``` **cURL (Google Cloud Storage)** ```bash curl -X POST "https://api.cloudlayer.io/v2/storage" \ -H "X-API-Key: your-api-key-here" \ -H "Content-Type: application/json" \ -d '{ "title": "GCS Bucket", "bucket": "my-gcs-bucket", "region": "us-central1", "accessKeyId": "GOOGTS7C7FUP3AIRVJTE2BCDKINBTES3HC2GY5CBFJDCQ2SYHIPAQKT6EXAMPLE", "secretAccessKey": "your-hmac-secret-key", "endpoint": "https://storage.googleapis.com" }' ``` **JavaScript (fetch)** ```javascript const response = await fetch("https://api.cloudlayer.io/v2/storage", { method: "POST", headers: { "X-API-Key": "your-api-key-here", "Content-Type": "application/json", }, body: JSON.stringify({ title: "Production S3", bucket: "my-company-pdfs", region: "us-east-1", accessKeyId: "AKIAIOSFODNN7EXAMPLE", secretAccessKey: "wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY", }), }); const result = await response.json(); console.log(result); ``` **Python (requests)** ```python import requests response = requests.post( "https://api.cloudlayer.io/v2/storage", headers={ "X-API-Key": "your-api-key-here", "Content-Type": "application/json", }, json={ "title": "Production S3", "bucket": "my-company-pdfs", "region": "us-east-1", "accessKeyId": "AKIAIOSFODNN7EXAMPLE", "secretAccessKey": "wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY", }, ) result = response.json() print(result) ``` #### Response ```json { "title": "Production S3", "id": "aBcDeFgHiJk123" } ``` --- ### Get Storage Configuration Retrieve your current storage configuration. Sensitive fields (`secretAccessKey`) are masked in the response. ``` GET /v2/storage ``` #### Examples **cURL** ```bash curl -X GET "https://api.cloudlayer.io/v2/storage" \ -H "X-API-Key: your-api-key-here" ``` **JavaScript (fetch)** ```javascript const response = await fetch("https://api.cloudlayer.io/v2/storage", { headers: { "X-API-Key": "your-api-key-here", }, }); const config = await response.json(); console.log(config); ``` **Python (requests)** ```python import requests response = requests.get( "https://api.cloudlayer.io/v2/storage", headers={"X-API-Key": "your-api-key-here"}, ) config = response.json() print(config) ``` #### Response ```json { "title": "Production S3", "bucket": "my-company-pdfs", "region": "us-east-1", "accessKeyId": "AKIA...MPLE", "secretAccessKey": "****", "endpoint": null } ``` --- ### Get Storage Configuration by ID Retrieve a specific storage configuration by its ID. ``` GET /v2/storage/:id ``` #### Path Parameters | Parameter | Type | Required | Description | | --- | --- | --- | --- | | `id` | string | Yes | The unique storage configuration ID. | #### Examples **cURL** ```bash curl -X GET "https://api.cloudlayer.io/v2/storage/aBcDeFgHiJk123" \ -H "X-API-Key: your-api-key-here" ``` **JavaScript (fetch)** ```javascript const response = await fetch("https://api.cloudlayer.io/v2/storage/aBcDeFgHiJk123", { headers: { "X-API-Key": "your-api-key-here", }, }); const config = await response.json(); console.log(config); ``` **Python (requests)** ```python import requests response = requests.get( "https://api.cloudlayer.io/v2/storage/aBcDeFgHiJk123", headers={"X-API-Key": "your-api-key-here"}, ) config = response.json() print(config) ``` #### Response ```json { "title": "Production S3", "bucket": "my-company-pdfs", "region": "us-east-1", "accessKeyId": "AKIA...MPLE", "secretAccessKey": "****", "endpoint": null } ``` --- ### Delete Storage Configuration Remove a storage configuration by its ID and revert to the default cloud storage. ``` DELETE /v2/storage/:id ``` #### Path Parameters | Parameter | Type | Required | Description | | --- | --- | --- | --- | | `id` | string | Yes | The unique storage configuration ID. | #### Examples **cURL** ```bash curl -X DELETE "https://api.cloudlayer.io/v2/storage/aBcDeFgHiJk123" \ -H "X-API-Key: your-api-key-here" ``` **JavaScript (fetch)** ```javascript const response = await fetch("https://api.cloudlayer.io/v2/storage/aBcDeFgHiJk123", { method: "DELETE", headers: { "X-API-Key": "your-api-key-here", }, }); const result = await response.json(); console.log(result); ``` **Python (requests)** ```python import requests response = requests.delete( "https://api.cloudlayer.io/v2/storage/aBcDeFgHiJk123", headers={"X-API-Key": "your-api-key-here"}, ) result = response.json() print(result) ``` #### Response ```json { "message": "Storage configuration deleted. Reverting to default cloud storage." } ``` --- ## S3-Compatible Services The following S3-compatible storage services have been tested and are fully supported: | Provider | Endpoint Format | Notes | | --- | --- | --- | | **AWS S3** | Omit `endpoint` (default) | Standard S3 service. Use IAM credentials with `s3:PutObject` permission on the target bucket. | | **Google Cloud Storage** | `https://storage.googleapis.com` | Requires [HMAC keys](https://cloud.google.com/storage/docs/authentication/hmackeys) for interoperability access. | | **DigitalOcean Spaces** | `https://{region}.digitaloceanspaces.com` | Spaces API keys from the DO control panel. | | **Backblaze B2** | `https://s3.{region}.backblazeb2.com` | B2 application keys with write access. | | **MinIO** | `https://your-minio-server.com` | Self-hosted S3-compatible storage. | | **Wasabi** | `https://s3.{region}.wasabisys.com` | Hot storage with no egress fees. | | **Cloudflare R2** | `https://{account-id}.r2.cloudflarestorage.com` | No egress fees. Use R2 API tokens. | --- ## IAM Policy for AWS S3 If using AWS S3, create an IAM user with the minimum required permissions: ```json { "Version": "2012-10-17", "Statement": [ { "Effect": "Allow", "Action": [ "s3:PutObject", "s3:PutObjectAcl" ], "Resource": "arn:aws:s3:::my-company-pdfs/*" } ] } ``` > **Security tip:** Scope the IAM policy to the specific bucket. Do not grant `s3:*` or access to other buckets. Use a dedicated IAM user for cloudlayer.io, not your root account credentials. --- ## How User Storage Works When user storage is configured: 1. You make an API request to generate a PDF or image (e.g., `POST /v2/html/pdf`). 2. cloudlayer.io generates the file as usual. 3. The file is uploaded to your S3-compatible bucket. 4. The API response includes the file as usual (for sync requests). 5. The [Assets](/docs/assets/) endpoint reflects the file's location in your bucket. --- ## Tips - **CORS:** If you need to access files from a browser, configure CORS on your S3 bucket to allow requests from your domain. - **Credentials rotation:** Rotate your S3 access keys periodically. Update the storage configuration via `POST /v2/storage` with new credentials. - **Testing:** After configuring user storage, generate a test file and verify it appears in your bucket at the expected location. - **Fallback:** If the upload to your bucket fails (e.g., invalid credentials, bucket does not exist), the API request will still succeed and the file will be available via cloudlayer.io cloud storage. Check your storage configuration if files are not appearing in your bucket. - **Reverting:** Delete your storage configuration (`DELETE /v2/storage/:id`) at any time to revert to the default cloud storage. ## Guides ### Visual Template Editor Source: https://cloudlayer.io/docs/visual-editor/ # Visual Template Editor The visual editor lets you design PDF and image templates by dragging and dropping elements onto a canvas — no HTML or CSS required. Bind data fields to elements, preview with sample data, and generate documents via the API or directly from the editor. ## Getting Started ### Creating a Template 1. Go to **Templates** → **New Template** 2. Choose output type: **PDF** or **Image** 3. Choose creation mode: - **Blank** — Start with an empty canvas - **AI Generate** — Describe what you want in plain text (e.g., "A professional invoice with line items, subtotals, and company logo") and the AI generates a starting template you can refine ### Editor Layout The editor has three main areas: - **Left Panel** — Layers, insertable elements, and data field management - **Center Canvas** — Your template. Drag, resize, and position elements here. - **Right Panel** — Property inspector for the selected element or page settings All panels are resizable and collapsible. Your layout preferences are saved automatically. --- ## Elements Insert elements from the toolbar or the left panel's Elements tab. ### Basic Elements | Element | Description | |---------|-------------| | **Text** | Editable text with full typography control. Supports data bindings like `{{ name }}`. | | **Rectangle** | Shape with fill color, border, rounded corners, and shadow. | | **Circle** | Round shape with fill, border, and shadow. | | **Line** | Straight line with stroke color and width. | | **Image** | Display an image from a URL. Supports cover, contain, and fill modes. | ### Data-Driven Elements | Element | Description | |---------|-------------| | **Table** | Define columns bound to data fields. Automatic row generation from arrays. | | **Chart** | Bar, line, pie, donut, or area chart. *(Coming soon — currently in preview.)* | | **QR Code** | Generate a QR code from static text or a data field. Configurable error correction. | | **Barcode** | Generate barcodes (Code128, EAN, UPC, etc.) from data. | ### Template Blocks Template blocks add logic to your design — loops, conditionals, and reusable sections. | Block | Description | Example | |-------|-------------|---------| | **For Loop** | Repeat elements for each item in a list | `for item in invoice.lineItems` | | **Conditional** | Show or hide elements based on data | `if showLogo` | | **Block** | Named reusable content section | `block header` | | **Macro** | Reusable template function | `macro badge(text, color)` | | **Set Variable** | Define a local variable | `set total = subtotal + tax` | Blocks appear as colored containers on the canvas. Drag elements inside them to make them part of the block. --- ## Working with Elements ### Selecting & Moving - **Click** an element to select it - **Drag** to reposition - **Resize** using corner and edge handles (hold **Shift** for aspect ratio lock) - **Ctrl+Click** to select multiple elements - **Ctrl+A** to select all - **Right-click** for context menu (duplicate, copy, cut, delete, reorder layers, lock, hide, group/ungroup) ### Alignment & Snapping - **Snap guides** appear automatically when elements align with each other - **Distance labels** show spacing between elements - Toggle snapping on/off from the toolbar - Enable **grid snap** to lock positioning to a pixel grid (configurable grid size: 2–100px) ### Grouping Select multiple elements → right-click → **Group**. Groups act as containers — move or resize the group and all children follow. Ungroup with **Ctrl+Shift+G**. ### Layers Panel The left panel's Layers tab shows all elements in z-order. From here you can: - **Reorder** elements (drag up/down to change layering) - **Rename** elements for clarity - **Show/hide** elements (eye icon) - **Lock/unlock** elements to prevent accidental edits (lock icon) --- ## Styling Elements Select any element and use the right panel's Property Inspector to style it. ### Text Properties - **Font** — Choose from web-safe fonts or search Google Fonts - **Size** — 1 to 999px - **Weight** — Thin (100) through Black (900) - **Style** — Normal, italic - **Decoration** — Underline, strikethrough - **Transform** — Uppercase, lowercase, capitalize - **Alignment** — Left, center, right, justify (horizontal); top, middle, bottom (vertical) - **Spacing** — Line height, letter spacing, word spacing - **Color** — Full color picker - **Padding** — Internal spacing - **Text Fitting** — None (default), shrink to fit, or grow to fit ### Shape & Common Properties - **Fill** — Background color with toggle - **Border** — Color, width (0–20px), style (solid, dashed, dotted), corner radius - **Shadow** — X/Y offset, blur, spread, color - **Opacity** — 0–100% - **Position** — X, Y coordinates (absolute) - **Size** — Width, height - **Rotation** — Angle in degrees ### Image Properties - **Source** — Image URL, file upload, or data variable binding - **Fit Mode** — Cover (fill, crop if needed), contain (fit within bounds), fill (stretch) - **Alt Text** — Accessibility description - **Filters** — Brightness, contrast, saturation, blur --- ## Data Binding The visual editor supports dynamic data — connect your template elements to data fields that get populated when you generate documents via the API. ### Defining Data Fields 1. Open the **Data** tab in the left panel 2. Click **Configure Data** to open the field builder 3. Add fields with a name and type (text, number, date, image URL, boolean, list) 4. Set default values for preview 5. Save — sample data is generated automatically ### Binding Data to Elements - **Drag** a data field from the left panel onto an element - Or select an element and pick a field from the **Data Source** section in the property inspector - Text elements use Nunjucks syntax: `{{ customerName }}`, `{{ invoice.total | formatCurrency }}` ### Variable Preview Mode Toggle **Preview Data** in the toolbar to see actual sample data values rendered in place of variable placeholders. This shows exactly how your document will look with real data. ### Template Syntax The editor uses [Nunjucks](https://mozilla.github.io/nunjucks/) template syntax: - **Variables:** `{{ variableName }}` - **Filters:** `{{ price | formatCurrency }}`, `{{ date | formatDateTime }}` - **Loops:** `{% for item in items %}...{% endfor %}` - **Conditionals:** `{% if showSection %}...{% endif %}` See the [Templating Guide](/docs/templating/) for full syntax reference and available helpers. --- ## Page Settings Click the page dimensions in the toolbar to configure: - **Paper Size** — Letter, A4, A3, Legal, Tabloid, and more - **Image Presets** — Open Graph (1200×630), Instagram (1080×1080), Twitter (1200×675), Facebook, Pinterest, YouTube Thumbnail, LinkedIn, Story - **Custom Size** — Set any width and height - **Orientation** — Portrait or landscape (toggle available for non-square sizes) - **Background Color** — Set via the property inspector when no element is selected - **Margins** — Top, right, bottom, left (0–500px) --- ## Preview & Generation ### Live Preview The editor shows a live preview of your template rendered with sample data. In split view, the preview updates as you make changes. ### Generating Documents Click **Generate** in the editor header to render your template as a PDF or image. The result opens in a new tab for download. To generate via the API, use the **API Code** button to see the exact endpoint and request body for your template. Copy it into your application code. ### Using Templates via API Once saved, use your template's ID with the conversion endpoints: ``` POST /v2/template/pdf { "templateId": "your-template-id", "data": { "customerName": "Acme Corp", "items": [...] } } ``` See [Template to PDF](/docs/template-to-pdf/) and [Template to Image](/docs/template-to-image/) for full API reference. --- ## Code Editor The editor also includes a full code editor for users who prefer writing HTML directly: - **HTML Tab** — Monaco code editor with Nunjucks syntax highlighting, autocomplete for template variables, and diagnostics for undefined variables - **Data Tab** — JSON editor for sample data with validation - **Options Tab** — PDF/image generation settings (format, viewport, etc.) Switch between Visual and HTML tabs at any time. Both modes work with the same template. --- ## Keyboard Shortcuts | Shortcut | Action | |----------|--------| | **V** | Select tool | | **H** | Hand/pan tool | | **T** | Text tool | | **R** | Rectangle tool | | **C** | Circle tool | | **L** | Line tool | | **Ctrl+S** | Save | | **Ctrl+Z** | Undo | | **Ctrl+Shift+Z** | Redo | | **Ctrl+D** | Duplicate selected | | **Delete** | Remove selected | | **Ctrl+G** | Group selected | | **Ctrl+Shift+G** | Ungroup | | **Ctrl+A** | Select all | Press **Ctrl+Shift+/** to see the full shortcut reference. --- ## Tips - **Use snap guides** for precise alignment. They appear as you drag elements near each other. - **Name your layers** — Double-click a layer name to rename it. Makes complex templates much easier to manage. - **Lock finished elements** — Click the lock icon to prevent accidental changes while you work on other parts - **Preview with real data** — Toggle Preview Data mode to catch layout issues with actual content lengths - **Start with AI** — Use AI Generate to get a starting template, then refine in the visual editor ### Templating Source: https://cloudlayer.io/docs/templating/ # Templating cloudlayer.io uses the [Nunjucks](https://mozilla.github.io/nunjucks/) templating engine to power its template-based document generation. With templates, you can create professional, data-driven PDFs and images, invoices, certificates, reports, receipts, and more, by combining HTML/CSS layouts with dynamic JSON data. ## Overview All templates support: - **Nunjucks templating syntax**, variables, conditionals, loops, filters, and macros - **Tailwind CSS via CDN**, link the CDN in your templates for utility-first styling - **Custom formatting functions**, `formatCurrency()`, `formatDateTime()`, `sumSubtotals()`, and `numToWords()` for multicultural formatting and calculations - **Auto calculations**, predefined templates can compute totals, taxes, and subtotals automatically - **Smart page breaking**, PDF templates handle page breaks intelligently for multi-page documents - **Thumbnail generation**, optionally generate image previews of your documents ## Template Types There are two ways to use templates with cloudlayer.io: predefined templates from the gallery, or custom templates you build yourself. ### Predefined Templates The fastest way to get started. Browse the template gallery, pick a design, and pass your data. 1. Choose a template from the [PDF Template Gallery](https://cloudlayer.io/templates/pdf/) or the [Image Template Gallery](https://cloudlayer.io/templates/image/). 2. Copy the `templateId` from the gallery page. 3. Use the sample JSON data as a starting point and customize it with your own values. ```json { "templateId": "professional-invoice", "data": { "company_name": "Acme Inc.", "invoice_no": "INV-001", "items": [ { "title": "Web Design", "quantity": 10, "unit_price": 150.00, "amount": null } ] } } ``` > **Tip:** Fields set to `null` in the sample data are auto-calculated by the template. Set `"__auto_calculate": false` in your data to supply your own values instead. ### Custom Templates For full control, write your own Nunjucks template and send it with your request. Custom templates are base64-encoded HTML strings that use Nunjucks syntax for dynamic content. ```json { "template": "PGh0bWw+PGJvZHk+SGVsbG8ge3tuYW1lfX0hPC9ib2R5PjwvaHRtbD4=", "data": { "name": "Alice" } } ``` The `template` value above is the base64-encoded version of: ```html Hello {{name}}! ``` ## Nunjucks Template Syntax ### Variables Use double curly braces to output dynamic values from your data. ```html

Invoice #{{invoice_no}}

Bill to: {{customer.name}}

Email: {{customer.email}}

``` Access nested properties with dot notation: ```html

{{company.address.street}}

{{company.address.city}}, {{company.address.state}}

``` ### Conditionals Use `{% if %}`, `{% elif %}`, and `{% else %}` for conditional rendering. ```html {% if status == "paid" %} Paid {% elif status == "pending" %} Pending {% else %} Overdue {% endif %} ``` You can also check for the existence of a value: ```html {% if discount %} Discount -{{formatCurrency(locale, currency, discount)}} {% endif %} ``` ### Loops Use `{% for %}` to iterate over arrays and objects. **Array iteration:** ```html {% for item in items %} {% endfor %}
Item Qty Price Amount
{{item.title}} {{item.quantity}} {{formatCurrency(locale, currency, item.unit_price)}} {{formatCurrency(locale, currency, item.amount)}}
``` **Object iteration:** ```html {% for key, value in metadata %}

{{key}}: {{value}}

{% endfor %} ``` **Loop variables:** Inside a `{% for %}` block, Nunjucks provides special variables: | Variable | Description | |-----------------|-------------------------------------------| | `loop.index` | Current iteration (1-based) | | `loop.index0` | Current iteration (0-based) | | `loop.first` | `true` if first iteration | | `loop.last` | `true` if last iteration | | `loop.length` | Total number of items | ```html {% for item in items %}
{{loop.index}}. {{item.title}}
{% endfor %} ``` ### Filters Nunjucks filters transform output values. Apply them with the pipe (`|`) character. **Common built-in filters:** | Filter | Example | Result | |----------------|------------------------------------|--------------------| | `upper` | `{{ "hello" | upper }}` | `HELLO` | | `lower` | `{{ "HELLO" | lower }}` | `hello` | | `capitalize` | `{{ "hello world" | capitalize }}`| `Hello world` | | `title` | `{{ "hello world" | title }}` | `Hello World` | | `trim` | `{{ " hello " | trim }}` | `hello` | | `replace` | `{{ "foo" | replace("o", "0") }}` | `f00` | | `truncate` | `{{ text | truncate(50) }}` | Truncated text... | | `round` | `{{ 4.567 | round(2) }}` | `4.57` | | `default` | `{{ missing | default("N/A") }}` | `N/A` | | `join` | `{{ items | join(", ") }}` | Comma-separated | | `length` | `{{ items | length }}` | Number of items | | `first` | `{{ items | first }}` | First item | | `last` | `{{ items | last }}` | Last item | | `safe` | `{{ htmlContent | safe }}` | Renders raw HTML | **Chaining filters:** ```html

{{ description | truncate(100) | capitalize }}

``` ## Custom Functions cloudlayer.io extends Nunjucks with four custom functions for formatting and calculations. ### `formatCurrency(locale, currency, amount)` Formats a numeric amount as a localized currency string. **Parameters:** | Parameter | Type | Description | |------------|----------|-----------------------------------------------------------------------------------------------------| | `locale` | `string` | Unicode locale identifier (e.g., `"en-US"`, `"en-GB"`, `"de-DE"`, `"ja-JP"`) | | `currency` | `string` | ISO 4217 currency code (e.g., `"USD"`, `"EUR"`, `"GBP"`, `"JPY"`) | | `amount` | `number` | The numeric amount to format | **Template usage:** ```html {{formatCurrency(locale, currency, item.unit_price)}} ``` **Examples:** | Locale | Currency | Amount | Output | |-----------|----------|----------|---------------| | `en-US` | `USD` | `1500.50`| `$1,500.50` | | `en-GB` | `EUR` | `150.10` | `€150.10` | | `de-DE` | `EUR` | `1234.56`| `1.234,56 €` | | `ja-JP` | `JPY` | `9800` | `¥9,800` | | `fr-FR` | `EUR` | `42.00` | `42,00 €` | ### `formatDateTime(locale, dateTime)` Formats a date string into a localized date representation. **Parameters:** | Parameter | Type | Description | |------------|----------|------------------------------------------------------------------------| | `locale` | `string` | Unicode locale identifier (e.g., `"en-US"`, `"fr-FR"`) | | `dateTime` | `string` | The string representation of the date | **Template usage:** ```html

Invoice Date: {{formatDateTime(locale, invoice_date)}}

``` **Examples:** | Locale | Input | Output | |-----------|---------------------|----------------------| | `en-US` | `"27 March, 2020"` | `March 27, 2020` | | `fr-FR` | `"27 March, 2020"` | `27 mars 2020` | | `de-DE` | `"27 March, 2020"` | `27. März 2020` | | `ja-JP` | `"27 March, 2020"` | `2020年3月27日` | ### `sumSubtotals(items)` Calculates the sum of all line item subtotals (unit price multiplied by quantity) in an array of items. **Parameters:** | Parameter | Type | Description | |-----------|---------|--------------------------------------------------------------------------------------------------| | `items` | `array` | Array of item objects, each with `unit_price` and `quantity` properties | **Template usage:** ```html {{formatCurrency(locale, currency, sumSubtotals(items))}} ``` Each item's subtotal is calculated as `unit_price * quantity` (quantity defaults to 1 if not provided), and all subtotals are summed together. ### `numToWords(locale, number)` Converts a numeric value into its word representation for the given locale. **Parameters:** | Parameter | Type | Description | |-----------|----------|------------------------------------------------------------------------| | `locale` | `string` | Unicode locale identifier (e.g., `"en-US"`, `"fr-FR"`) | | `number` | `number` | The numeric value to convert to words | **Template usage:** ```html

Total in words: {{numToWords(locale, amount)}}

``` **Examples:** | Locale | Input | Output | |-----------|---------|--------------------------------------| | `en-US` | `1590` | `One Thousand Five Hundred Ninety` | | `en-US` | `42` | `Forty Two` | ## Smart Page Breaking for PDFs PDF templates support intelligent page breaking so that content flows naturally across multiple pages without awkward mid-element splits. ### CSS Page Break Properties Use standard CSS page break properties in your templates: ```css /* Always start a new page before this element */ .section-header { page-break-before: always; } /* Avoid breaking inside this element */ .invoice-item { page-break-inside: avoid; } /* Avoid a page break right after this element */ .section-title { page-break-after: avoid; } ``` ### Practical Example: Multi-Page Invoice ```html
{% for item in items %} {% endfor %}
{{item.title}} {{item.quantity}} {{formatCurrency(locale, currency, item.unit_price)}}

Subtotal: {{formatCurrency(locale, currency, subTotal)}}

Tax: {{formatCurrency(locale, currency, totalTax)}}

Total: {{formatCurrency(locale, currency, amount)}}

Terms & Conditions

{{terms}}

``` ## Tailwind CSS Support All templates support Tailwind CSS. Link the CDN in your template's `` to use utility classes: ```html

{{company_name}}

Invoice # {{invoice_no}}
``` > **Tip:** Since cloudlayer.io fetches external resources during rendering, any publicly accessible CSS framework, font, or stylesheet can be used in your templates, not just Tailwind. ## Auto Calculations Predefined templates from the gallery may support auto calculations. When enabled, the template automatically computes derived values like line item totals, subtotals, taxes, and grand totals. ### How It Works 1. **Auto-calculated fields** are represented as `null` in the sample data. 2. When `__auto_calculate` is `true` (the default), the template computes these fields. 3. Set `__auto_calculate` to `false` if you want to supply your own computed values. ```json { "templateId": "professional-invoice", "data": { "__auto_calculate": true, "tax_percentage": 6, "items": [ { "title": "Web Design", "quantity": 10, "unit_price": 150.00, "amount": null } ], "subTotal": null, "totalTax": null, "amount": null } } ``` In this example, `amount`, `subTotal`, `totalTax`, and the item-level `amount` are all computed automatically: - Item amount = `quantity * unit_price` = `1,500.00` - Subtotal = sum of item amounts = `1,500.00` - Tax = `subTotal * (tax_percentage / 100)` = `90.00` - Total = `subTotal + totalTax` = `1,590.00` To disable auto calculation and supply your own values: ```json { "templateId": "professional-invoice", "data": { "__auto_calculate": false, "items": [ { "title": "Web Design", "quantity": 10, "unit_price": 150.00, "amount": 1500.00 } ], "subTotal": 1500.00, "totalTax": 90.00, "amount": 1590.00 } } ``` ## Template Data Injection Data is injected into templates via the `data` property in your API request. The data object's keys become variables available in the template. ### Inline Request Send the template and data as part of a single JSON payload: ```json { "templateId": "professional-invoice", "data": { "company_name": "Acme Inc.", "invoice_no": "INV-001", "locale": "en-US", "currency": "USD", "items": [...] } } ``` ### Multipart Request For larger templates, use `multipart/form-data` to send the template as a file: ```shell curl --request POST \ --url https://api.cloudlayer.io/v2/template/pdf \ --header 'Content-Type: multipart/form-data' \ --header 'x-api-key: ' \ --form 'template=@./template.njk' \ --form 'data=@./invoice-data.json' ``` With multipart requests, the template file does not need to be base64-encoded. The `template` field contains the Nunjucks template file and the `data` field contains the JSON data file. ## Practical Examples ### Invoice Template A complete invoice template using Tailwind CSS, Nunjucks loops, and the custom formatting functions. **Template (HTML):** ```html

{{company_name}}

{{address1}}

{{city}}, {{state}} {{zip}}

INVOICE

#{{invoice_no}}

{{formatDateTime(locale, invoice_date)}}

Bill To

{{bill_to_fullname}}

{{bill_to_address1}}

{{bill_to_city}}, {{bill_to_state_province_region}} {{bill_to_zip_postal_code}}

{% for item in items %} {% endfor %}
Description Qty Unit Price Amount

{{item.title}}

{% if item.description %}

{{item.description | truncate(80)}}

{% endif %}
{{item.quantity}} {{formatCurrency(locale, currency, item.unit_price)}} {{formatCurrency(locale, currency, item.amount)}}
Subtotal {{formatCurrency(locale, currency, subTotal)}}
{{tax_label}} ({{tax_percentage}}%) {{formatCurrency(locale, currency, totalTax)}}
Total {{formatCurrency(locale, currency, amount)}}
{% if notes %}

Notes

{{notes}}

{% endif %}
``` **Sample data:** ```json { "templateId": "professional-invoice", "data": { "__auto_calculate": true, "locale": "en-US", "currency": "USD", "company_name": "Acme Inc.", "address1": "1711 Bushnell Avenue", "city": "South Pasadena", "state": "California", "zip": "91030", "invoice_no": "INV-621", "invoice_date": "27 March, 2020", "bill_to_fullname": "Terry G. Brown", "bill_to_address1": "1680 Ralph Drive", "bill_to_city": "Cleveland", "bill_to_state_province_region": "OH", "bill_to_zip_postal_code": "44114", "tax_label": "Tax", "tax_percentage": 6, "items": [ { "title": "Homepage Design", "description": "Complete redesign of the main landing page", "quantity": 40, "unit_price": 150.10, "amount": null }, { "title": "Backend Development", "description": "API development and database setup", "quantity": 40, "unit_price": 150.00, "amount": null }, { "title": "QA Testing", "description": "End-to-end testing and bug fixes", "quantity": 10, "unit_price": 10.00, "amount": null } ], "subTotal": null, "totalTax": null, "amount": null, "notes": "Payment is due within 30 days. Please include the invoice number with your payment." } } ``` ### Certificate Template A simple certificate of completion using custom styling. **Template (HTML):** ```html

Certificate of Completion

{{recipient_name}}

has successfully completed the course

{{course_name}}

Awarded on {{formatDateTime(locale, completion_date)}}

{{instructor_name}}

Instructor

{{organization}}

Organization

``` **Sample data:** ```json { "template": "", "data": { "locale": "en-US", "recipient_name": "Jane Smith", "course_name": "Advanced Web Development", "completion_date": "15 January, 2024", "instructor_name": "Dr. Alex Johnson", "organization": "Tech Academy" } } ``` ## Request Types ### Inline Request Send everything as a single JSON payload. The template must be base64-encoded. ```shell curl --request POST \ --url https://api.cloudlayer.io/v2/template/pdf \ --header 'Content-Type: application/json' \ --header 'x-api-key: ' \ --data '{ "templateId": "professional-invoice", "data": { "company_name": "Acme Inc.", ... } }' ``` ### Multipart Request Send the template and data as separate files. The template does not need to be base64-encoded. ```shell curl --request POST \ --url https://api.cloudlayer.io/v2/template/pdf \ --header 'Content-Type: multipart/form-data' \ --header 'x-api-key: ' \ --form 'template=@./my-template.njk' \ --form 'data=@./my-data.json' ``` ## Tips and Best Practices - **Use predefined templates** when possible, they are professionally designed and tested for print quality. - **Test with small data sets** first, then scale up to production data volumes. - **Use `page-break-inside: avoid`** on table rows and key sections to prevent awkward splits in PDFs. - **Keep images external** (hosted URLs) when possible for smaller payload sizes, or embed as data URIs for fully self-contained documents. - **Use `formatCurrency` and `formatDateTime`** instead of manual formatting to ensure correct localization across all locales. - **Set margins to `0`** when using templates (this is the default for template-based generation) and control spacing with CSS padding instead. - **Use the `safe` filter** (`{{ htmlContent | safe }}`) when you need to render raw HTML stored in your data, but only with trusted content. ### Webhooks & Async Processing Source: https://cloudlayer.io/docs/webhooks/ # Webhooks & Async Processing cloudlayer.io supports both synchronous and asynchronous processing modes. Understanding when to use each, and how to configure webhooks for async results, is key to building reliable document generation workflows. ## Sync vs Async Processing ### Synchronous Mode (`async: false`) In synchronous mode, the API waits for the document to be generated and returns the result directly in the HTTP response. ```json { "url": "https://example.com", "async": false } ``` **Response (v2):** ```json { "id": "1705312200000", "status": "success", "assetUrl": "https://storage.cloudlayer.io/assets/abc123/document.pdf", "timestamp": 1705312200000 } ``` > **Note:** v1 synchronous requests return the raw binary file (PDF, image, etc.) directly in the response body, not JSON. The JSON response shown above applies only to v2. **Characteristics:** - The HTTP connection stays open until the document is ready - The `assetUrl` is populated in the response - Simpler to implement, no webhook setup needed - Subject to connection timeouts for long-running jobs ### Asynchronous Mode (`async: true`, default) In asynchronous mode, the API immediately returns a job ID and processes the document in the background. When complete, the result is delivered to the `webhook` URL you provide in the request. ```json { "url": "https://example.com", "async": true, "webhook": "https://yourserver.com/webhooks/cloudlayer" } ``` **Immediate response:** ```json { "status": "pending", "id": "1705312200000" } ``` **Webhook delivery (when complete):** ```json { "id": "1705312200000", "status": "success", "type": "url-pdf", "assetUrl": "https://storage.cloudlayer.io/assets/abc123/document.pdf", "previewUrl": "https://storage.cloudlayer.io/assets/abc123/document-preview.webp", "processTime": 2340, "size": 184320, "timestamp": 1705312200000 } ``` > **Note:** The webhook payload includes the full job object. Additional internal fields (cost data, worker info, etc.) may also be present but are not part of the public API contract. **Characteristics:** - The HTTP connection closes immediately after queuing - No risk of connection timeouts - Results delivered via webhook callback - Better for long-running and batch operations ## When to Use Each Mode | Scenario | Recommended Mode | Reason | |-------------------------------------------|------------------|----------------------------------------------| | Simple, fast conversions (single page) | Sync | Simpler implementation, immediate result | | User-facing "download PDF" button | Sync | User expects immediate response | | Batch processing (multiple documents) | Async | Avoids timeouts, handles volume | | Large or complex documents | Async | May exceed sync timeout limits | | Background/scheduled generation | Async | No user waiting for the result | | High-volume automated pipelines | Async | Better throughput, reliable delivery | | Pages with heavy JavaScript rendering | Async | Render time may be unpredictable | ## Webhook Configuration ### Providing Your Webhook URL In v2 of the API, you provide a webhook URL directly in each request using the `webhook` parameter. When the async job completes, cloudlayer.io sends the result to that URL. ```json { "url": "https://example.com", "async": true, "webhook": "https://yourserver.com/webhooks/cloudlayer" } ``` The `webhook` URL must use HTTPS. ### Webhook Endpoint Requirements Your endpoint must: 1. Accept HTTP POST requests 2. Return a `200` status code to acknowledge receipt 3. Process the webhook payload ### Webhook Payload When a job completes, cloudlayer.io sends a POST request to your webhook URL with a JSON body: **Success payload:** ```json { "id": "1705312200000", "status": "success", "type": "url-pdf", "assetUrl": "https://storage.cloudlayer.io/assets/abc123/document.pdf", "previewUrl": "https://storage.cloudlayer.io/assets/abc123/document-preview.webp", "processTime": 2340, "size": 184320, "timestamp": 1705312200000 } ``` **Error payload:** ```json { "id": "1705312200000", "status": "error", "type": "url-pdf", "error": "Timeout: page took longer than 30000ms to load", "timestamp": 1705312205000 } ``` ### Payload Fields | Field | Type | Description | |---------------|----------|-------------------------------------------------------------------| | `id` | `string` | Unique identifier for the generation job | | `status` | `string` | Job outcome: `success` or `error` | | `type` | `string` | The job type (e.g., `url-pdf`, `html-image`, `template-pdf`) | | `assetUrl` | `string` | URL to the generated document (on success) | | `previewUrl` | `string` | URL to the preview thumbnail, if `generatePreview` was used | | `processTime` | `number` | Processing time in milliseconds | | `size` | `number` | File size of the generated document in bytes | | `error` | `string` | Error description (on failure) | | `timestamp` | `number` | Unix epoch timestamp in milliseconds of when the job completed | ### Example Webhook Handler #### Node.js (Express) ```javascript app.post("/webhooks/cloudlayer", (req, res) => { const { id, status, assetUrl, error } = req.body; if (status === "success") { console.log(`Job ${id} completed: ${assetUrl}`); // Process the generated document -- download it, email it, etc. } else { console.error(`Job ${id} failed: ${error}`); // Handle the error -- retry, notify, log, etc. } // Always return 200 to acknowledge receipt res.status(200).send("OK"); }); ``` #### Python (Flask) ```python from flask import Flask, request app = Flask(__name__) @app.route("/webhooks/cloudlayer", methods=["POST"]) def handle_webhook(): payload = request.get_json() job_id = payload["id"] status = payload["status"] if status == "success": asset_url = payload["assetUrl"] print(f"Job {job_id} completed: {asset_url}") # Process the generated document else: error = payload.get("error", "Unknown error") print(f"Job {job_id} failed: {error}") # Handle the error return "OK", 200 ``` ## Webhook Delivery cloudlayer.io makes a **single delivery attempt** to your webhook URL. There are no automatic retries. If your endpoint is unreachable or returns a non-`200` status code, the webhook delivery fails silently, the job status is **not affected**. The job remains `success` or `error` based on the generation result. If webhook delivery fails, you can still retrieve the job result via the [Jobs API](/docs/jobs/). ### Ensuring Reliable Delivery - **Return `200` quickly.** Process the webhook payload asynchronously (e.g., push to a queue) rather than doing heavy work inside the handler. - **Make your handler idempotent.** Use the `id` to deduplicate in case of unexpected duplicate deliveries. - **Implement your own fallback.** Since there are no automatic retries, consider polling the Jobs API as a backup if webhook delivery is critical to your workflow. - **Log all webhook deliveries** for debugging and auditing. ## Best Practices ### Use Async for Production Pipelines Synchronous mode is convenient for development and simple use cases, but async with webhooks is more robust for production: - No risk of HTTP timeouts cutting off long-running jobs - Your application does not need to hold open connections - Better scalability for high-volume generation ### Correlate Jobs with Your Data Pass a `projectId` or use a naming convention in your `filename` to correlate generated documents with your internal records: ```json { "url": "https://example.com/invoice/INV-001", "filename": "invoice-INV-001.pdf", "projectId": "N77VCoAVTHHwgmUfCYlD", "async": true } ``` When the webhook fires, use the `id` to look up which internal record triggered the generation. ### Handle Errors Gracefully Your webhook handler should account for: - **Timeout errors**, the page took too long to render. Consider increasing the `timeout` parameter or simplifying the page. - **Navigation errors**, the URL was unreachable. Verify the URL is accessible and correctly spelled. - **Rendering errors**, the page had JavaScript errors or missing resources. Test the page in a browser first. ### Monitor Webhook Health - Set up alerts for webhook delivery failures - Track success/failure ratios over time - Use a service like [Hookdeck](https://hookdeck.com) or [ngrok](https://ngrok.com) for local webhook testing during development ### Privacy & Data Handling Source: https://cloudlayer.io/docs/privacy/ # Privacy & Data Handling This guide explains the product controls that affect data handling. It is technical documentation, not a substitute for the [Privacy Notice](/privacy/) or [Data Processing Addendum](/dpa/). Cloudlayer’s [Subprocessor List](/subprocessors/) identifies providers, data categories, roles, and processing locations. The [Security](/security/) page records the security posture without claiming an unverified certification. ## Roles and responsibility Cloudlayer acts as a processor or service provider when it handles content for your configured workflow. You decide what to capture, upload, collect, generate, sign, deliver, and retain, and you remain responsible for the notices, rights, permissions, lawful basis, and data minimization that your use requires. Cloudlayer separately acts as controller for account administration, billing, security, support, and its own website and service operations. The Privacy Notice explains that distinction and how a person can exercise a privacy right. ## Data paths A Cloudlayer workflow can involve several data classes: | Data | Examples | Control point | | ------------------- | ---------------------------------------------------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------- | | Source content | HTML, CSS, a URL, cookies, headers, template variables, uploaded files, or imported documents | Send only content you are authorized to use; avoid unnecessary personal data and secrets | | Workflow records | Job state, artifact references, form definitions, submissions, signature events, delivery attempts, audit events, and errors | Workspace roles, tenant scope, retention, export, deletion, and audit controls | | Output | PDF, image, video, captured page, completed form document, or signed record | Cloudlayer storage, customer storage, synchronous return, recipient delivery, and configured expiry | | Destinations | Customer S3-compatible storage, Google Cloud Storage, webhook, email recipient, or integration | You choose the provider, account, endpoint, region, permissions, and transfer basis | | AI input and output | Prompt, selected source content, instructions, suggestion, and feedback | An AI feature sends only the selected material to the disclosed model provider; review all output | Customer-directed storage and webhooks are not Cloudlayer-appointed subprocessors. They receive data only after you configure the destination. Test destination permissions with non-sensitive data before using it in production. ## Storage choices Choose a storage mode according to the workflow and your retention obligations. | Mode | Behavior | Use when | | ----------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------- | | Cloudlayer-managed storage | Stores the artifact under the workspace’s access and retention controls | The artifact must remain available in Cloudlayer for download, delivery, audit, or later workflow steps | | Customer-directed storage | Sends the artifact to a configured storage provider and region | Your organization manages the destination, lifecycle, access, and residency decision | | Secure or zero-retention mode | Returns or delivers the result without making it a Cloudlayer-durable artifact; bounded operational records and temporary processing can still exist | The workflow does not need a retained Cloudlayer artifact and the selected API path supports the mode | For a synchronous request that supports it, `storage: false` asks Cloudlayer not to retain the generated output as a managed asset: ```json { "async": false, "storage": false } ``` This setting does not mean that no data is processed. Cloudlayer still has to admit, secure, render, meter, and return the job. Bounded job, security, audit, billing, failure, and legal records can remain under their own retention rules. Temporary payloads remain only through the processing and recovery horizon and are then removed. For a customer-directed destination, reference the saved configuration rather than sending storage credentials in each job: ```json { "storage": { "id": "your-storage-config-id" } } ``` The storage adapter supports S3-compatible destinations and Google Cloud Storage where configured. Availability and limits follow your plan and order. A custom endpoint must pass the same destination, network, timeout, and secret-redaction checks as a named provider. ## Synchronous and asynchronous processing Synchronous work holds the request open until the result or terminal error is returned. Asynchronous work records an accepted job, processes it through the queue, and exposes status and delivery results. Retries use the same logical job and artifact identity so a retry does not create an additional billed result. Both paths can create operational metadata. Output storage is a separate choice from job history, audit evidence, billing records, and security logs. A webhook receives only the bounded completion or failure contract sent to the endpoint you configured. ## Secrets and capture credentials URLs, proxy credentials, basic-auth values, cookies, headers, API keys, storage credentials, and webhook secrets require different handling from ordinary template variables. - Use saved encrypted configurations or secret controls where the product provides them. - Grant only the permissions and lifetime needed for the job or destination. - Do not place private keys or account passwords in HTML, template data, prompts, filenames, or support messages. - Rotate a credential after suspected disclosure and revoke the affected API key or destination. - Treat a webpage capture as access by your organization. Supplying a cookie or credential does not establish the legal right to use it. Cloudlayer redacts secret fields from customer-visible history, errors, and application logs where those fields can appear. Cloudlayer does not promise that arbitrary personal data embedded in free text can always be recognized. Do not include a secret in an unrestricted text field. ## Access and tenant controls Data belongs to an account, organization, or workspace scope. Membership and role checks apply to templates, forms, submissions, contacts, artifacts, storage configurations, signatures, workflows, and audit history. Creator attribution does not replace tenant authorization. Use separate API keys for separate systems and environments. Give service identities the minimum workspace and operation scope. Remove members and revoke keys when access is no longer required. Public forms, recipient links, and signing sessions use purpose-bound access rather than exposing a workspace credential. ## Forms and signatures A form owner controls questions, prefills, attachments, respondents, consent text, and retention. Collect only what the stated workflow needs. Do not put confidential values in a prefilled URL. Use authentication and save/resume controls that fit the sensitivity of the response. Signature event history records the actions needed for workflow and audit evidence. That evidence does not decide whether a document or authentication method satisfies the law for a particular transaction. The [Terms of Service](/terms/) explain the customer’s responsibility for electronic consent, signer authority, record delivery, and transaction-specific formalities. ## AI features An AI feature sends the prompt and source material selected for that operation to the model provider identified in the Subprocessor List. Do not submit sensitive or regulated data unless the feature and your order expressly permit it. Check facts, calculations, rights, bias, accessibility, and legal or technical requirements before using the output. See the [AI Additional Terms](/ai-terms/). ## Retention, export, and deletion Retention depends on the data class, selected storage mode, workspace policy, plan, legal hold, and configured workflow. Product controls expose expiry, deletion, trash or recovery, and export where applicable. Customer-directed output follows the destination’s policy after delivery. Account cancellation stops renewal but does not silently destroy Customer Data. Account deletion, workspace deletion, individual resource deletion, and retention expiry are distinct operations. Some billing, security, consent, dispute, backup, and audit records can remain for a limited legal or recovery purpose with use restricted to that purpose. Plan your lifecycle before collecting data: 1. classify the source, respondent, signer, and output data; 2. select the minimum storage and history needed; 3. configure workspace access and customer destinations; 4. test export, deletion, destination failure, and credential rotation; and 5. document how your organization answers access, correction, deletion, and objection requests. ## Regulated data Cloudlayer is not enabled for protected health information or electronic protected health information. Do not send PHI or ePHI unless Cloudlayer has approved an eligible service scope in writing and both parties have executed the required business associate agreement. Do not send payment-card data through templates, forms, captures, or ordinary API fields. Payment entry belongs in Stripe-hosted controls. Other sensitive data requires the relevant product support, contract, legal basis, and safeguards. ## SDKs ### SDKs Overview Source: https://cloudlayer.io/docs/sdks/ # PDF Generation SDKs cloudlayer.io provides official PDF generation SDKs for .NET (C# and F#), Go, Java, JavaScript/TypeScript, PHP, Python, and Ruby. Convert HTML to PDF, capture URLs as documents, and render templates from your application. Since all document generation is driven by a REST API, you can also integrate directly from any language that supports HTTP requests. ## Official SDKs | Language | Package | Source | |----------|---------|--------| | [.NET C#](/docs/sdk-dotnet/) | [NuGet](https://www.nuget.org/packages/cloudlayerio-dotnet/) | [GitHub](https://github.com/cloudlayerio/cloudlayerio-dotnet) | | [Go](/docs/sdk-go/) | [pkg.go.dev](https://pkg.go.dev/github.com/cloudlayerio/cloudlayerio-go) | [GitHub](https://github.com/cloudlayerio/cloudlayerio-go) | | [Java](/docs/sdk-java/) | [Maven Central](https://central.sonatype.com/artifact/io.cloudlayer/cloudlayerio-java) | [GitHub](https://github.com/cloudlayerio/cloudlayerio-java) | | [JavaScript / TypeScript](/docs/sdk-javascript/) | [npm](https://www.npmjs.com/package/@cloudlayerio/sdk) | [GitHub](https://github.com/cloudlayerio/cloudlayerio-js) | | [PHP](/docs/sdk-php/) | [Packagist](https://packagist.org/packages/cloudlayerio/cloudlayerio-php) | [GitHub](https://github.com/cloudlayerio/cloudlayerio-php) | | [Python](/docs/sdk-python/) | [PyPI](https://pypi.org/project/cloudlayerio/) | [GitHub](https://github.com/cloudlayerio/cloudlayerio-python) | | [Ruby](/docs/sdk-ruby/) | [RubyGems](https://rubygems.org/gems/cloudlayerio) | [GitHub](https://github.com/cloudlayerio/cloudlayerio-ruby) | | .NET F# | [NuGet](https://www.nuget.org/packages/Cloudlayer.FSharp/) | [GitHub](https://github.com/cloudlayerio/cloudlayerio-fsharp) | ## Clients in development Rust and Swift clients exist as source on [GitHub](https://github.com/cloudlayerio) and are not yet published to a package manager: [Rust](https://github.com/cloudlayerio/rust) and [Swift](https://github.com/cloudlayerio/swift). Until they ship, call the REST API directly from those languages. ## Using the REST API Directly The cloudlayer.io REST API can be called from any language. All you need is: 1. Your API key (passed via the `X-API-Key` header) 2. The ability to make HTTP POST requests with JSON bodies 3. Base URL: `https://api.cloudlayer.io/v2` ### Quick Reference: API Endpoints | Endpoint | Description | |-----------------------|----------------------------------------| | `POST /url/pdf` | Convert a URL to PDF | | `POST /url/image` | Convert a URL to an image | | `POST /html/pdf` | Convert HTML to PDF | | `POST /html/image` | Convert HTML to an image | | `POST /template/pdf` | Generate a PDF from a template | | `POST /template/image`| Generate an image from a template | | `GET /url/pdf` | Simple URL to PDF (query params only) | | `GET /url/image` | Simple URL to image (query params only) | ### cURL Example ```shell curl --request POST \ --url https://api.cloudlayer.io/v2/url/pdf \ --header 'Content-Type: application/json' \ --header 'X-API-Key: ' \ --data '{ "url": "https://example.com", "async": false }' ``` ### JavaScript (Node.js / Bun / Deno) ```javascript const response = await fetch("https://api.cloudlayer.io/v2/url/pdf", { method: "POST", headers: { "Content-Type": "application/json", "X-API-Key": "", }, body: JSON.stringify({ url: "https://example.com", async: false, }), }); const result = await response.json(); console.log(result.assetUrl); ``` ### Python ```python import requests response = requests.post( "https://api.cloudlayer.io/v2/url/pdf", headers={ "Content-Type": "application/json", "X-API-Key": "", }, json={ "url": "https://example.com", "async": False, }, ) result = response.json() print(result["assetUrl"]) ``` > **Tip:** Even if your language does not have a dedicated SDK, the REST API is straightforward to use. See the [Authentication Examples](/docs/authentication-examples/), [HTML Examples](/docs/html-examples/), and [URL Examples](/docs/url-examples/) for more language-specific code samples. ### .NET C# SDK Source: https://cloudlayer.io/docs/sdk-dotnet/ # .NET C# PDF Generation SDK The official `cloudlayerio-dotnet` package provides C# PDF generation for .NET 6+ applications. Convert HTML to PDF, capture URLs as documents, and render templates — available on NuGet with full IntelliSense and typed request/response models. - **NuGet Package:** [cloudlayerio-dotnet](https://www.nuget.org/packages/cloudlayerio-dotnet/) - **Source Code:** [github.com/cloudlayerio/cloudlayerio-dotnet](https://github.com/cloudlayerio/cloudlayerio-dotnet) ## Installation ### Visual Studio Using the Package Manager Console: ```powershell Install-Package cloudlayerio-dotnet ``` Or search for `cloudlayerio-dotnet` in the [NuGet Package Manager](https://docs.microsoft.com/en-us/nuget/consume-packages/install-use-packages-visual-studio). ### JetBrains Rider See [JetBrains NuGet documentation](https://www.jetbrains.com/help/rider/Using_NuGet.html). ### .NET CLI ```shell dotnet add package cloudlayerio-dotnet ``` ## Setup Get your API key by creating a free account at [cloudlayer.io](https://beta-app.cloudlayer.io/auth/sign-up). The free account includes a monthly render allowance, with no card. See [pricing](https://cloudlayer.io/pricing/) for the current figures. Initialize the manager with your API key: ```csharp var manager = new CloudlayerioManager(""); ``` ## URL to PDF ```csharp var rsp = await manager.UrlToPdf(new UrlToPdf { Url = "https://example.com", Async = false }); // Save the JSON response to the filesystem await rsp.SaveToFileSystem("C:\\output\\example.json"); // Access the asset URL (available when async is false) var url = rsp.Response.AssetUrl; ``` ## URL to Image ```csharp var rsp = await manager.UrlToImage(new UrlToImage { Url = "https://example.com", Async = false, AutoScroll = true, ViewPort = new ViewPort { Width = 1440, Height = 900, DeviceScaleFactor = 2 } }); var url = rsp.Response.AssetUrl; ``` ## HTML to PDF ```csharp // HTML must be base64-encoded var html = Convert.ToBase64String( Encoding.UTF8.GetBytes("

Hello!

") ); var rsp = await manager.HtmlToPdf(new HtmlToPdf { Html = html }); await rsp.SaveToFileSystem("C:\\output\\hello.json"); ``` ## Template to PDF ```csharp var rsp = await manager.TemplateToPdf(new TemplateToPdf { TemplateId = "professional-invoice", Data = new Dictionary { ["company_name"] = "Acme Inc.", ["invoice_no"] = "INV-001", ["locale"] = "en-US", ["currency"] = "USD", ["items"] = new[] { new Dictionary { ["title"] = "Web Design", ["quantity"] = 10, ["unit_price"] = 150.00, ["amount"] = null } } } }); await rsp.SaveToFileSystem("C:\\output\\invoice.json"); ``` ## Synchronous vs Asynchronous By default, requests are processed asynchronously. The response is delivered to your configured webhook. To get the result immediately in the response, set `Async = false`: ```csharp var rsp = await manager.UrlToPdf(new UrlToPdf { Url = "https://example.com", Async = false }); // AssetUrl is populated in the synchronous response var url = rsp.Response.AssetUrl; ``` > **Note:** For long-running requests (large documents, batch processing), use the default async mode with webhooks to avoid connection timeouts. ## Advanced Options The SDK provides typed classes for all API options, with full IntelliSense support. ### Custom Margins ```csharp var rsp = await manager.UrlToPdf(new UrlToPdf { Url = "https://example.com", Margin = new Margin { Top = new LayoutDimension(UnitTypes.Pixels, 100), Bottom = new LayoutDimension(UnitTypes.Pixels, 100), Left = new LayoutDimension(UnitTypes.Pixels, 50), Right = new LayoutDimension(UnitTypes.Pixels, 50) } }); ``` ### Header and Footer Templates ```csharp var rsp = await manager.UrlToPdf(new UrlToPdf { Url = "https://example.com", Margin = new Margin { Top = new LayoutDimension(UnitTypes.Pixels, 120), Bottom = new LayoutDimension(UnitTypes.Pixels, 80) }, HeaderTemplate = new HeaderFooterTemplate { Method = "extract", Selector = ".page-header", Style = new Dictionary { ["width"] = "100%", ["text-align"] = "center" } }, FooterTemplate = new HeaderFooterTemplate { Selector = ".page-footer", Style = new Dictionary { ["width"] = "100%", ["font-size"] = "10px", ["text-align"] = "center" } } }); ``` ### Viewport Configuration ```csharp var rsp = await manager.UrlToImage(new UrlToImage { Url = "https://example.com", ViewPort = new ViewPort { Width = 1920, Height = 1080, DeviceScaleFactor = 2, IsMobile = false, IsLandscape = true } }); ``` ## Response Format As of v2, the API returns a JSON response rather than binary content. The `SaveToFileSystem` helper saves this JSON response to disk. The response object is fully typed with properties including: | Property | Type | Description | |---------------|----------|----------------------------------------------------------| | `AssetUrl` | `string` | URL to the generated asset (populated for sync calls) | | `Status` | `string` | Job status (`success`, `error`, etc.) | | `JobId` | `string` | Unique identifier for the generation job | For the full set of response properties and request options, see the [source code](https://github.com/cloudlayerio/cloudlayerio-dotnet/tree/main/cloudlayerio-dotnet). ### JavaScript SDK Source: https://cloudlayer.io/docs/sdk-javascript/ # JavaScript PDF Generation SDK The official `@cloudlayerio/sdk` provides JavaScript PDF generation for Node.js, Bun, and browser environments. Convert HTML to PDF, capture URLs as documents, and render templates — with full TypeScript type safety, zero runtime dependencies, and dual ESM/CJS support. - **npm Package:** [@cloudlayerio/sdk](https://www.npmjs.com/package/@cloudlayerio/sdk) - **Source Code:** [github.com/cloudlayerio/cloudlayerio-js](https://github.com/cloudlayerio/cloudlayerio-js) ## Installation ```bash npm install @cloudlayerio/sdk ``` ```bash yarn add @cloudlayerio/sdk ``` ```bash pnpm add @cloudlayerio/sdk ``` **Requirements:** Node.js 20+ (uses native `fetch`) or any modern browser. ## Setup Get your API key by creating a free account at [cloudlayer.io](https://beta-app.cloudlayer.io/auth/sign-up). The free account includes a monthly render allowance, with no card. See [pricing](https://cloudlayer.io/pricing/) for the current figures. ```typescript import { CloudLayer } from "@cloudlayerio/sdk"; const client = new CloudLayer({ apiKey: "", apiVersion: "v2", }); ``` The SDK requires you to choose an API version: | | v1 | v2 | |---|---|---| | **Default mode** | Synchronous (returns binary) | Asynchronous (returns Job) | | **Sync response** | Raw binary (PDF/image) | JSON Job object | | **Binary access** | Direct from response | Via `downloadJobResult()` | ## URL to PDF ### v2 (recommended) v2 returns a Job object. Use `downloadJobResult()` to get the binary: ```typescript const { data: job } = await client.urlToPdf({ url: "https://example.com", format: "a4", margin: { top: "20px", bottom: "20px" }, }); // Download the PDF binary const pdfBuffer = await client.downloadJobResult(job); ``` ### v1 (legacy) v1 returns the binary directly: ```typescript const v1 = new CloudLayer({ apiKey: "", apiVersion: "v1" }); const { data: pdfBuffer } = await v1.urlToPdf({ url: "https://example.com", }); ``` ## URL to Image ```typescript const { data } = await client.urlToImage({ url: "https://example.com", imageType: "png", quality: 90, }); ``` ## HTML to PDF HTML content must be Base64-encoded: ```typescript const html = btoa("

Hello World

"); const { data: job } = await client.htmlToPdf({ html }); ``` ## HTML to Image ```typescript const html = btoa("

Screenshot

"); const { data } = await client.htmlToImage({ html, imageType: "webp", quality: 85, }); ``` ## Template to PDF ```typescript const { data: job } = await client.templateToPdf({ templateId: "professional-invoice", data: { company_name: "Acme Inc.", invoice_no: "INV-001", items: [{ title: "Web Design", quantity: 10, unit_price: 150.0 }], }, }); ``` ## PDF Merge ```typescript const { data: job } = await client.mergePdfs({ batch: { urls: [ "https://example.com/page1.pdf", "https://example.com/page2.pdf", ], }, }); ``` ## Batch Processing Process up to 20 URLs in one request (always async): ```typescript const { data: job } = await client.urlToPdf({ batch: { urls: ["https://a.com", "https://b.com", "https://c.com"], }, storage: true, }); ``` ## Async Mode & Job Polling ```typescript const { data: job } = await client.urlToPdf({ url: "https://example.com", storage: true, }); // Poll until complete (5s interval, 5 min timeout) const completed = await client.waitForJob(job.id); // Download the result const pdf = await client.downloadJobResult(completed); ``` ## Data Management ```typescript // Jobs const jobs = await client.listJobs(); const job = await client.getJob("job-id"); // Assets const assets = await client.listAssets(); const asset = await client.getAsset("asset-id"); // Storage const storages = await client.listStorage(); await client.addStorage({ title: "My S3", region: "us-east-1", accessKeyId: "AKIA...", secretAccessKey: "...", bucket: "my-bucket", }); // Account const account = await client.getAccount(); const status = await client.getStatus(); ``` ## Error Handling ```typescript import { CloudLayerAuthError, CloudLayerRateLimitError, CloudLayerApiError, CloudLayerTimeoutError, CloudLayerValidationError, } from "@cloudlayerio/sdk"; try { await client.urlToPdf({ url: "https://example.com" }); } catch (error) { if (error instanceof CloudLayerAuthError) { console.error("Invalid API key"); } else if (error instanceof CloudLayerRateLimitError) { console.error(`Rate limited. Retry after ${error.retryAfter}s`); } else if (error instanceof CloudLayerApiError) { console.error(`API error ${error.status}: ${error.message}`); } else if (error instanceof CloudLayerTimeoutError) { console.error(`Timed out after ${error.timeout}ms`); } else if (error instanceof CloudLayerValidationError) { console.error(`Invalid input: ${error.field}`); } } ``` ## Advanced Options ### Viewport & Puppeteer ```typescript const { data } = await client.urlToPdf({ url: "https://example.com", viewPort: { width: 1920, height: 1080, deviceScaleFactor: 2, isMobile: false, hasTouch: false, isLandscape: false, }, waitUntil: "networkidle0", cookies: [{ name: "session", value: "abc123", domain: "example.com" }], authentication: { username: "user", password: "pass" }, }); ``` ### Custom Margins & Headers/Footers ```typescript const { data } = await client.urlToPdf({ url: "https://example.com", format: "letter", margin: { top: "1in", bottom: "1in", left: "0.5in", right: "0.5in" }, printBackground: true, headerTemplate: { method: "extract", selector: ".page-header", }, footerTemplate: { method: "template", template: "
Page
", }, }); ``` ## TypeScript Full type safety for all request options and responses: ```typescript import type { UrlToPdfOptions, Job, AccountInfo, CloudLayerResponseHeaders, } from "@cloudlayerio/sdk"; ``` ### Python SDK Source: https://cloudlayer.io/docs/sdk-python/ # Python PDF Generation SDK The official `cloudlayerio` package provides Python PDF generation with sync and async support. Convert HTML to PDF, capture URLs as documents, and render templates — with full type safety (mypy strict) and a single runtime dependency (`httpx`). - **PyPI Package:** [cloudlayerio](https://pypi.org/project/cloudlayerio/) - **Source Code:** [github.com/cloudlayerio/cloudlayerio-python](https://github.com/cloudlayerio/cloudlayerio-python) ## Installation ```bash pip install cloudlayerio ``` **Requirements:** Python 3.9+ ## Setup Get your API key by creating a free account at [cloudlayer.io](https://beta-app.cloudlayer.io/auth/sign-up). The free account includes a monthly render allowance, with no card. See [pricing](https://cloudlayer.io/pricing/) for the current figures. ```python from cloudlayerio import CloudLayer client = CloudLayer("YOUR-API-KEY", api_version="v2") ``` The SDK requires you to choose an API version: | | v1 | v2 | |---|---|---| | **Default mode** | Synchronous (returns binary) | Asynchronous (returns Job) | | **Sync response** | Raw binary (PDF/image bytes) | JSON Job object | | **Binary access** | Direct from `result.data` | Via `download_job_result()` | ## URL to PDF ### v2 (recommended) v2 returns a Job object. Use `download_job_result()` to get the binary: ```python with CloudLayer("YOUR-API-KEY", api_version="v2") as client: result = client.url_to_pdf({"url": "https://example.com"}) completed = client.wait_for_job(result.data.id) pdf_bytes = client.download_job_result(completed) with open("output.pdf", "wb") as f: f.write(pdf_bytes) ``` ### v1 (direct binary) v1 returns the binary directly: ```python with CloudLayer("YOUR-API-KEY", api_version="v1") as client: result = client.url_to_pdf({"url": "https://example.com"}) with open("output.pdf", "wb") as f: f.write(result.data) # bytes ``` ## HTML to PDF ```python import base64 html = base64.b64encode(b"

Hello World

").decode() result = client.html_to_pdf({"html": html}) ``` ## Template Rendering ```python result = client.template_to_pdf( { "template_id": "your-template-id", "data": {"name": "John", "total": "$100"}, } ) ``` ## Async Client ```python import asyncio from cloudlayerio import AsyncCloudLayer async def main(): async with AsyncCloudLayer("YOUR-API-KEY", api_version="v2") as client: result = await client.url_to_pdf({"url": "https://example.com"}) completed = await client.wait_for_job(result.data.id) pdf_bytes = await client.download_job_result(completed) asyncio.run(main()) ``` ## Error Handling ```python from cloudlayerio.errors import ( CloudLayerAuthError, CloudLayerRateLimitError, CloudLayerError, ) try: result = client.url_to_pdf({"url": "https://example.com"}) except CloudLayerAuthError: print("Invalid API key") except CloudLayerRateLimitError as e: print(f"Rate limited, retry after {e.retry_after}s") except CloudLayerError as e: print(f"Error: {e}") ``` ## Full Documentation See the [README on GitHub](https://github.com/cloudlayerio/cloudlayerio-python) for complete documentation including all conversion methods, data management, storage configuration, and performance notes. ### PHP SDK Source: https://cloudlayer.io/docs/sdk-php/ # PHP PDF Generation SDK The official `cloudlayerio/cloudlayerio-php` package provides PHP PDF generation for any PHP 8.1+ application. Convert HTML to PDF, capture URLs as documents, and render templates — with full type safety (PHPStan level 8), strict types, and a single runtime dependency (`guzzlehttp/guzzle`). - **Packagist Package:** [cloudlayerio/cloudlayerio-php](https://packagist.org/packages/cloudlayerio/cloudlayerio-php) - **Source Code:** [github.com/cloudlayerio/cloudlayerio-php](https://github.com/cloudlayerio/cloudlayerio-php) ## Installation ```bash composer require cloudlayerio/cloudlayerio-php ``` **Requirements:** PHP 8.1+ ## Setup Get your API key by creating a free account at [cloudlayer.io](https://beta-app.cloudlayer.io/auth/sign-up). The free account includes a monthly render allowance, with no card. See [pricing](https://cloudlayer.io/pricing/) for the current figures. ```php use CloudLayer\CloudLayer; $client = new CloudLayer( apiKey: 'YOUR-API-KEY', apiVersion: 'v2', ); ``` The SDK requires you to choose an API version: | | v1 | v2 | |---|---|---| | **Default mode** | Synchronous (returns binary) | Asynchronous (returns Job) | | **Sync response** | Raw binary (PDF/image bytes) | JSON Job object | | **Binary access** | Direct from `$result->data` | Via `downloadJobResult()` | ## URL to PDF ### v2 (recommended) v2 returns a Job object. Use `downloadJobResult()` to get the binary: ```php $result = $client->urlToPdf(['url' => 'https://example.com']); $job = $client->waitForJob($result->data->id); $pdfBytes = $client->downloadJobResult($job); file_put_contents('output.pdf', $pdfBytes); ``` ### v1 (direct binary) v1 returns the binary directly: ```php $client = new CloudLayer(apiKey: 'YOUR-API-KEY', apiVersion: 'v1'); $result = $client->urlToPdf(['url' => 'https://example.com']); file_put_contents('output.pdf', $result->data); // raw PDF bytes ``` ## HTML to PDF ```php $html = base64_encode('

Hello World

'); $result = $client->htmlToPdf(['html' => $html]); ``` ## Template Rendering ```php $result = $client->templateToPdf([ 'templateId' => 'your-template-id', 'data' => ['name' => 'John', 'total' => '$100'], ]); ``` ## Error Handling ```php use CloudLayer\Errors\AuthException; use CloudLayer\Errors\RateLimitException; use Cloudlayer\Errors\CloudLayerException; try { $result = $client->urlToPdf(['url' => 'https://example.com']); } catch (AuthException $e) { echo "Invalid API key\n"; } catch (RateLimitException $e) { echo "Rate limited, retry after {$e->retryAfter}s\n"; } catch (CloudLayerException $e) { echo "Error: {$e->getMessage()}\n"; } ``` ## Full Documentation See the [README on GitHub](https://github.com/cloudlayerio/cloudlayerio-php) for complete documentation including all conversion methods, data management, storage configuration, and performance notes. ### Go SDK Source: https://cloudlayer.io/docs/sdk-go/ # Go PDF Generation SDK The official `cloudlayerio-go` package provides Go PDF generation with context support and zero external dependencies. Convert HTML to PDF, capture URLs as documents, and render templates — using only the Go standard library. - **Go Package:** [github.com/cloudlayerio/cloudlayerio-go](https://pkg.go.dev/github.com/cloudlayerio/cloudlayerio-go) - **Source Code:** [github.com/cloudlayerio/cloudlayerio-go](https://github.com/cloudlayerio/cloudlayerio-go) ## Installation ```bash go get github.com/cloudlayerio/cloudlayerio-go ``` **Requirements:** Go 1.21+, zero external dependencies ## Setup Get your API key by creating a free account at [cloudlayer.io](https://beta-app.cloudlayer.io/auth/sign-up). The free account includes a monthly render allowance, with no card. See [pricing](https://cloudlayer.io/pricing/) for the current figures. ```go import cloudlayer "github.com/cloudlayerio/cloudlayerio-go" client, err := cloudlayer.NewClient("YOUR-API-KEY", cloudlayer.V2) ``` The SDK requires you to choose an API version: | | v1 | v2 | |---|---|---| | **Default mode** | Synchronous (returns binary) | Asynchronous (returns Job) | | **Sync response** | Raw binary (PDF/image bytes) | JSON Job object | | **Binary access** | Direct from `result.Data` | Via `DownloadJobResult()` | ## URL to PDF ### v2 (recommended) v2 returns a Job object. Use `DownloadJobResult()` to get the binary: ```go result, err := client.URLToPDF(ctx, &cloudlayer.URLToPDFOptions{ URLOptions: cloudlayer.URLOptions{ URL: cloudlayer.StringPtr("https://example.com"), }, }) if err != nil { log.Fatal(err) } // Wait for the job to complete job, err := client.WaitForJob(ctx, result.Job.ID) if err != nil { log.Fatal(err) } // Download the PDF binary data, err := client.DownloadJobResult(ctx, job) if err != nil { log.Fatal(err) } os.WriteFile("output.pdf", data, 0644) ``` ### v1 (legacy) v1 returns raw binary directly: ```go client, _ := cloudlayer.NewClient("YOUR-API-KEY", cloudlayer.V1) result, err := client.URLToPDF(ctx, &cloudlayer.URLToPDFOptions{ URLOptions: cloudlayer.URLOptions{ URL: cloudlayer.StringPtr("https://example.com"), }, }) if err != nil { log.Fatal(err) } os.WriteFile("output.pdf", result.Data, 0644) ``` ## HTML to PDF ```go result, err := client.HTMLToPDF(ctx, &cloudlayer.HTMLToPDFOptions{ HTMLOptions: cloudlayer.HTMLOptions{ HTML: cloudlayer.EncodeHTML("

Hello World

Generated by cloudlayer.io

"), }, PDFOptions: cloudlayer.PDFOptions{ Format: &cloudlayer.FormatA4, PrintBackground: cloudlayer.BoolPtr(true), }, }) ``` ## Template to PDF ```go result, err := client.TemplateToPDF(ctx, &cloudlayer.TemplateToPDFOptions{ TemplateOptions: cloudlayer.TemplateOptions{ TemplateID: cloudlayer.StringPtr("professional-invoice"), Data: map[string]interface{}{ "invoiceNumber": "INV-2024-001", "companyName": "Acme Corp", "items": []map[string]interface{}{ {"name": "Widget A", "quantity": 10, "price": 25.00}, }, "total": 250.00, }, }, }) ``` ## Error Handling All errors are concrete types supporting `errors.As`: ```go result, err := client.HTMLToPDF(ctx, opts) if err != nil { var authErr *cloudlayer.AuthError if errors.As(err, &authErr) { log.Fatal("Invalid API key") } var rateLimitErr *cloudlayer.RateLimitError if errors.As(err, &rateLimitErr) { log.Printf("Rate limited, retry after %ds", *rateLimitErr.RetryAfter) } log.Fatal(err) } ``` | Error Type | When | |------------|------| | `*AuthError` | Invalid or missing API key (401/403) | | `*RateLimitError` | Rate limit exceeded (429) | | `*APIError` | Other API errors | | `*ValidationError` | Invalid input parameters | | `*NetworkError` | Connection failures | ## All Conversion Methods | Method | Description | |--------|-------------| | `URLToPDF()` | Convert a URL to PDF | | `URLToImage()` | Screenshot a URL | | `HTMLToPDF()` | Convert HTML to PDF | | `HTMLToImage()` | Convert HTML to image | | `TemplateToPDF()` | Render template as PDF | | `TemplateToImage()` | Render template as image | | `MergePDFs()` | Merge multiple PDFs | ## Data Management ```go // List recent jobs jobs, _ := client.ListJobs(ctx) // Get account usage account, _ := client.GetAccount(ctx) // Configure custom storage client.AddStorage(ctx, &cloudlayer.StorageParams{ Title: "My S3 Bucket", Bucket: "my-pdfs", Region: "us-east-1", AccessKeyID: "AKIA...", SecretAccessKey: "...", }) ``` ### Java SDK Source: https://cloudlayer.io/docs/sdk-java/ # Java PDF Generation SDK The official `cloudlayerio-java` library provides Java PDF generation with builder pattern options, automatic retry logic, and thread-safe clients. Convert HTML to PDF, capture URLs as documents, and render templates — with only Jackson as a runtime dependency. - **Maven Central:** [io.cloudlayer:cloudlayerio-java](https://central.sonatype.com/artifact/io.cloudlayer/cloudlayerio-java) - **Source Code:** [github.com/cloudlayerio/cloudlayerio-java](https://github.com/cloudlayerio/cloudlayerio-java) ## Installation ### Maven ```xml io.cloudlayer cloudlayerio-java 0.1.0 ``` ### Gradle ```groovy implementation 'io.cloudlayer:cloudlayerio-java:0.1.0' ``` **Requirements:** Java 11+, only Jackson for JSON (no other runtime dependencies) ## Setup Get your API key by creating a free account at [cloudlayer.io](https://beta-app.cloudlayer.io/auth/sign-up). The free account includes a monthly render allowance, with no card. See [pricing](https://cloudlayer.io/pricing/) for the current figures. ```java import io.cloudlayer.sdk.*; import io.cloudlayer.sdk.model.endpoint.*; import io.cloudlayer.sdk.model.constants.*; import io.cloudlayer.sdk.model.response.*; CloudLayer client = CloudLayer.builder("YOUR-API-KEY", ApiVersion.V2).build(); ``` The SDK requires you to choose an API version: | | v1 | v2 | |---|---|---| | **Default mode** | Synchronous (returns binary) | Asynchronous (returns Job) | | **Sync response** | Raw binary (PDF/image bytes) | JSON Job object | | **Binary access** | Direct from `result.getBytes()` | Via `downloadJobResult()` | ## URL to PDF ### v2 (recommended) v2 returns a Job object. Use `waitForJob()` + `downloadJobResult()` to get the binary: ```java ConversionResult result = client.urlToPdf(UrlToPdfOptions.builder() .url("https://example.com") .format(PdfFormat.A4) .printBackground(true) .build()); Job job = client.waitForJob(result.getJob().getId()); byte[] pdf = client.downloadJobResult(job); Files.write(Path.of("output.pdf"), pdf); ``` ### v1 v1 returns binary directly: ```java CloudLayer v1Client = CloudLayer.builder("YOUR-API-KEY", ApiVersion.V1).build(); ConversionResult result = v1Client.urlToPdf(UrlToPdfOptions.builder() .url("https://example.com") .format(PdfFormat.A4) .build()); byte[] pdf = result.getBytes(); ``` ## URL to Image ```java ConversionResult result = client.urlToImage(UrlToImageOptions.builder() .url("https://example.com") .imageType(ImageType.PNG) .quality(90) .build()); Job job = client.waitForJob(result.getJob().getId()); byte[] image = client.downloadJobResult(job); ``` ## HTML to PDF HTML must be Base64-encoded. Use the included `HtmlUtil.encodeHtml()` helper: ```java import io.cloudlayer.sdk.util.HtmlUtil; String html = "

Hello World

"; ConversionResult result = client.htmlToPdf(HtmlToPdfOptions.builder() .html(HtmlUtil.encodeHtml(html)) .format(PdfFormat.LETTER) .printBackground(true) .build()); ``` ## Template to PDF ```java ConversionResult result = client.templateToPdf(TemplateToPdfOptions.builder() .templateId("your-template-id") .data(Map.of( "name", "John Doe", "invoiceNumber", "INV-001", "amount", 1500 )) .build()); ``` ## Merge PDFs ```java ConversionResult result = client.mergePdfs(MergePdfsOptions.builder() .batch(Batch.of(List.of( "https://example.com/doc1.pdf", "https://example.com/doc2.pdf" ))) .build()); ``` ## Data Management ```java // Jobs List jobs = client.listJobs(); Job job = client.getJob("job-id"); // Assets List assets = client.listAssets(); byte[] data = client.downloadJobResult(job); // Account AccountInfo account = client.getAccount(); StatusResponse status = client.getStatus(); // Templates List templates = client.listTemplates(); PublicTemplate template = client.getTemplate("template-id"); ``` ## Configuration Options ```java CloudLayer client = CloudLayer.builder("key", ApiVersion.V2) .timeout(Duration.ofSeconds(60)) // request timeout (default: 30s) .maxRetries(3) // retry count, range [0, 5] (default: 2) .userAgent("my-app/1.0") // custom user agent .headers(Map.of("X-Custom", "value")) // additional headers .httpClient(customHttpClient) // inject custom java.net.http.HttpClient .build(); ``` ## Error Handling ```java try { client.urlToPdf(options); } catch (AuthException e) { // 401 or 403 } catch (RateLimitException e) { // 429 — check e.getRetryAfterSeconds() } catch (ValidationException e) { // Client-side validation error } catch (ApiException e) { // Other HTTP error — e.getStatusCode(), e.getResponseBody() } catch (TimeoutException e) { // Request or poll timeout } catch (NetworkException e) { // Connection failure } ``` ## Working with v2 Results The recommended v2 workflow: ```java // 1. Start conversion (returns immediately with pending Job) ConversionResult result = client.urlToPdf(options); // 2. Wait for completion (polls every 5s, timeout 5min by default) Job job = client.waitForJob(result.getJob().getId()); // 3. Download the binary byte[] pdf = client.downloadJobResult(job); // Custom polling options Job job = client.waitForJob(jobId, WaitForJobOptions.builder() .interval(Duration.ofSeconds(3)) .maxWait(Duration.ofMinutes(10)) .build()); ``` ### Ruby SDK Source: https://cloudlayer.io/docs/sdk-ruby/ # Ruby SDK The official Ruby gem for the [cloudlayer.io](https://cloudlayer.io) visual document and image platform. Convert HTML, URLs, and templates to PDF and images, manage jobs, assets, and storage configurations. **[RubyGems](https://rubygems.org/gems/cloudlayerio)** · **[GitHub Source](https://github.com/cloudlayerio/cloudlayerio-ruby)** ## Installation Add to your Gemfile: ```ruby gem "cloudlayerio", "~> 0.1" ``` Then run: ```bash bundle install ``` Or install directly: ```bash gem install cloudlayerio ``` ## Setup ```ruby require "cloudlayerio" client = CloudLayerio::Client.new( api_key: "your-api-key", api_version: :v2 ) ``` Get your API key from the [cloudlayer.io dashboard](https://beta-app.cloudlayer.io). **API Versions:** - `:v1` — synchronous, returns binary PDF/image data directly - `:v2` — asynchronous, returns a Job object (poll for completion, then download) ## URL to PDF ```ruby # v2 (async) result = client.url_to_pdf( url: "https://example.com", format: "a4", print_background: true, async: true, storage: true ) job = client.wait_for_job(result.job.id) data = client.download_job_result(job) File.binwrite("output.pdf", data) ``` ```ruby # v1 (sync) — returns binary directly v1_client = CloudLayerio::Client.new(api_key: "your-key", api_version: :v1) result = v1_client.url_to_pdf(url: "https://example.com") File.binwrite("output.pdf", result.bytes) ``` ## URL to Image ```ruby result = client.url_to_image( url: "https://example.com", image_type: "png", quality: 90 ) ``` ## HTML to PDF ```ruby html = CloudLayerio::Util::HtmlUtil.encode_html(<<~HTML)

Invoice #001

Thank you for your purchase.

HTML result = client.html_to_pdf( html: html, format: "letter", print_background: true ) ``` ## HTML to Image ```ruby html = CloudLayerio::Util::HtmlUtil.encode_html("

Hello

") result = client.html_to_image(html: html, image_type: "png") ``` ## Template to PDF ```ruby result = client.template_to_pdf( template_id: "your-template-id", data: { company: "Acme Corp", invoice_number: "INV-001", items: [{ name: "Widget", price: 9.99 }] } ) ``` ## Template to Image ```ruby result = client.template_to_image( template_id: "your-template-id", data: { title: "Certificate of Completion" }, image_type: "png" ) ``` ## Merge PDFs ```ruby result = client.merge_pdfs( batch: CloudLayerio::Options::Batch.new(urls: [ "https://example.com/page1.pdf", "https://example.com/page2.pdf" ]) ) ``` ## Data Management ### Jobs & Assets ```ruby jobs = client.list_jobs # Up to 10 most recent job = client.get_job("job-id") assets = client.list_assets # Up to 10 most recent asset = client.get_asset("asset-id") ``` ### Account ```ruby account = client.get_account puts "Calls: #{account.calls}/#{account.calls_limit}" ``` ### Templates ```ruby templates = client.list_templates(type: "pdf", category: "invoice") template = client.get_template("template-id") ``` ### Storage ```ruby storages = client.list_storage detail = client.get_storage("storage-id") resp = client.add_storage( title: "My S3", region: "us-east-1", access_key_id: "AKIA...", secret_access_key: "...", bucket: "my-bucket" ) client.delete_storage("storage-id") ``` ## Configuration Options | Option | Type | Default | Description | |--------|------|---------|-------------| | `api_key` | String | *required* | Your cloudlayer.io API key | | `api_version` | Symbol | *required* | `:v1` or `:v2` | | `base_url` | String | `https://api.cloudlayer.io` | API base URL | | `timeout` | Numeric | `30` | Request timeout (seconds) | | `max_retries` | Integer | `2` | Retry attempts for 429/5xx (0-5) | | `user_agent` | String | `cloudlayerio-ruby/VERSION` | User-Agent header | | `headers` | Hash | `{}` | Additional HTTP headers | ## Error Handling ```ruby begin result = client.url_to_pdf(url: "https://example.com") rescue CloudLayerio::AuthError => e puts "Auth failed (#{e.status_code}): #{e.message}" rescue CloudLayerio::RateLimitError => e puts "Rate limited, retry after #{e.retry_after}s" rescue CloudLayerio::ApiError => e puts "API error #{e.status_code}: #{e.message}" rescue CloudLayerio::TimeoutError puts "Request timed out" rescue CloudLayerio::NetworkError puts "Connection failed" rescue CloudLayerio::ValidationError => e puts "Invalid input: #{e.message}" end ``` ## Requirements - Ruby >= 3.1 - One runtime dependency: `base64` gem (bundled in Ruby stdlib; extracted in Ruby 3.4+) ## Examples ### Authentication Source: https://cloudlayer.io/docs/authentication-examples/ # Authentication When generating PDFs or images from URLs, your target page may be behind authentication. cloudlayer.io supports two authentication methods for accessing protected pages: - **Basic Auth**, HTTP Basic Authentication using a username and password - **Cookie Auth**, Session cookies that establish an authenticated user state Both methods work with all URL-based endpoints (`/url/pdf`, `/url/image`). ## Basic Auth If your URL is protected behind [HTTP Basic Authentication](https://developer.mozilla.org/en-US/docs/Web/HTTP/Authentication), pass the credentials using the `authentication` object. ### JSON Payload ```json { "url": "https://internal.example.com/dashboard", "authentication": { "username": "admin", "password": "secretpassword" } } ``` ### Parameters | Parameter | Type | Required | Description | |------------------------------|----------|----------|----------------------------| | `authentication.username` | `string` | Yes | The username for Basic Auth | | `authentication.password` | `string` | Yes | The password for Basic Auth | ### cURL Example ```shell curl --request POST \ --url https://api.cloudlayer.io/v2/url/pdf \ --header 'Content-Type: application/json' \ --header 'x-api-key: ' \ --data '{ "url": "https://internal.example.com/dashboard", "authentication": { "username": "admin", "password": "secretpassword" }, "async": false }' ``` ### JavaScript Example ```javascript const response = await fetch("https://api.cloudlayer.io/v2/url/pdf", { method: "POST", headers: { "Content-Type": "application/json", "x-api-key": "", }, body: JSON.stringify({ url: "https://internal.example.com/dashboard", authentication: { username: "admin", password: "secretpassword", }, async: false, }), }); const result = await response.json(); console.log(result.assetUrl); ``` ### Python Example ```python import requests response = requests.post( "https://api.cloudlayer.io/v2/url/pdf", headers={ "Content-Type": "application/json", "x-api-key": "", }, json={ "url": "https://internal.example.com/dashboard", "authentication": { "username": "admin", "password": "secretpassword", }, "async": False, }, ) result = response.json() print(result["assetUrl"]) ``` ### Practical Example: Capturing a Protected Dashboard A common use case is generating PDF reports from internal dashboards that require login: ```json { "url": "https://analytics.yourcompany.com/reports/monthly", "authentication": { "username": "report-bot", "password": "bot-secret-key" }, "format": "letter", "landscape": true, "printBackground": true, "margin": { "top": "0.5in", "bottom": "0.5in", "left": "0.5in", "right": "0.5in" }, "waitUntil": "networkidle0", "delay": 2000, "async": false } ``` This configuration: 1. Authenticates to the dashboard with Basic Auth credentials 2. Waits for all network requests to complete (`networkidle0`) 3. Adds an extra 2-second delay for any JavaScript-rendered charts to finish 4. Generates a letter-sized landscape PDF with backgrounds and custom margins ## Cookie Auth For sites that use session-based authentication (login forms, OAuth, etc.), you can pass session cookies to establish an authenticated state before the page is loaded. ### JSON Payload ```json { "url": "https://app.example.com/account", "cookies": [ { "name": "_session_id", "value": "ad9a8u90f0df7d87fdas9fa892342", "domain": "app.example.com", "path": "/", "expires": 1768752280, "httpOnly": true, "secure": true } ] } ``` ### Cookie Parameters | Parameter | Type | Required | Description | |-------------|-----------|----------|---------------------------------------------------------------------| | `name` | `string` | Yes | Cookie name | | `value` | `string` | Yes | Cookie value | | `domain` | `string` | No | Host the cookie is sent to. Defaults to the URL's host. | | `path` | `string` | No | Path that must exist in the URL for the cookie to be sent | | `url` | `string` | No | URL to associate with the cookie (usually left empty) | | `expires` | `number` | No | Cookie expiration as a Unix timestamp | | `httpOnly` | `boolean` | No | Prevent JavaScript from accessing the cookie | | `secure` | `boolean` | No | Only send the cookie over HTTPS | | `sameSite` | `string` | No | Cross-origin policy: `"Strict"` or `"Lax"` | ### cURL Example ```shell curl --request POST \ --url https://api.cloudlayer.io/v2/url/pdf \ --header 'Content-Type: application/json' \ --header 'x-api-key: ' \ --data '{ "url": "https://app.example.com/account", "cookies": [ { "name": "_session_id", "value": "ad9a8u90f0df7d87fdas9fa892342", "domain": "app.example.com", "path": "/", "expires": 1768752280, "httpOnly": true } ], "async": false }' ``` ### JavaScript Example ```javascript const response = await fetch("https://api.cloudlayer.io/v2/url/pdf", { method: "POST", headers: { "Content-Type": "application/json", "x-api-key": "", }, body: JSON.stringify({ url: "https://app.example.com/account", cookies: [ { name: "_session_id", value: "ad9a8u90f0df7d87fdas9fa892342", domain: "app.example.com", path: "/", httpOnly: true, }, ], async: false, }), }); const result = await response.json(); console.log(result.assetUrl); ``` ### Python Example ```python import requests response = requests.post( "https://api.cloudlayer.io/v2/url/pdf", headers={ "Content-Type": "application/json", "x-api-key": "", }, json={ "url": "https://app.example.com/account", "cookies": [ { "name": "_session_id", "value": "ad9a8u90f0df7d87fdas9fa892342", "domain": "app.example.com", "path": "/", "httpOnly": True, }, ], "async": False, }, ) result = response.json() print(result["assetUrl"]) ``` ### Multiple Cookies You can pass multiple cookies in a single request to replicate a full browser session: ```json { "url": "https://app.example.com/dashboard", "cookies": [ { "name": "_session_id", "value": "abc123def456", "domain": "app.example.com", "path": "/", "httpOnly": true }, { "name": "cookie_consent", "value": "accepted", "domain": "app.example.com", "path": "/" }, { "name": "theme", "value": "dark", "domain": "app.example.com", "path": "/" } ] } ``` ### How to Obtain Session Cookies To capture cookies from an authenticated session: 1. Log in to the target site in your browser. 2. Open **Developer Tools** (F12 or Ctrl+Shift+I). 3. Go to the **Application** tab (Chrome) or **Storage** tab (Firefox). 4. Navigate to **Cookies** and select the site's domain. 5. Copy the relevant cookie name/value pairs (look for session identifiers like `_session_id`, `connect.sid`, `PHPSESSID`, etc.). > **Note:** Session cookies typically have a limited lifespan. For automated workflows, consider using a dedicated API token or service account instead of short-lived session cookies. ## Security and Privacy cloudlayer.io takes authentication credentials seriously: - **Credentials are never stored.** Authentication usernames, passwords, and cookie values are redacted from all logs and database records. - **Activity logs show `...`** in place of any sensitive information. - **Use HTTPS URLs** whenever passing authentication credentials to ensure they are encrypted in transit to the target page. - **Use short-lived credentials** or service accounts with minimal permissions for automated workflows. For more details, see the [Privacy & Data Handling](/docs/privacy/) guide. ## Tips - **Basic Auth is simpler** and preferred when the target site supports it. Cookie auth requires managing session state. - **Use `waitUntil: "networkidle0"`** with authenticated pages to ensure all authenticated API calls complete before capture. - **Add a `delay`** (in milliseconds) if the page has JavaScript-heavy content that takes time to render after authentication. - **Test your authentication** by first trying the URL in a browser with the same credentials to verify the page loads correctly. - **For single-page apps (SPAs)**, cookie auth combined with `waitForSelector` is often more reliable than a simple delay. ### Working with HTML Source: https://cloudlayer.io/docs/html-examples/ # Working with HTML Converting HTML to PDFs and images gives you complete control over the output. You can use any valid HTML, CSS, JavaScript, Sass, and externally hosted resources like fonts, stylesheets, and images. As long as the base64-encoded payload is under 10MB, cloudlayer.io will accept and convert it. ## HTML Encoding HTML must be **base64-encoded** before being included in the JSON request body. This avoids issues with special characters (double quotes, angle brackets, etc.) that would otherwise break JSON parsing. **Original HTML:** ```html

Hello world!

``` **Base64-encoded:** ``` PGh0bWw+PGJvZHk+PGgxPkhlbGxvIHdvcmxkITwvaDE+PC9ib2R5PjwvaHRtbD4= ``` **JSON payload:** ```json { "html": "PGh0bWw+PGJvZHk+PGgxPkhlbGxvIHdvcmxkITwvaDE+PC9ib2R5PjwvaHRtbD4=" } ``` ### Encoding HTML in Different Languages #### JavaScript (Node.js / Browser) ```javascript // Browser const encoded = btoa("

Hello!

"); // Node.js / Bun const encoded = Buffer.from( "

Hello!

" ).toString("base64"); ``` #### Python ```python import base64 html = "

Hello!

" encoded = base64.b64encode(html.encode("utf-8")).decode("utf-8") ``` #### C\# ```csharp using System.Text; var html = "

Hello!

"; var encoded = Convert.ToBase64String(Encoding.UTF8.GetBytes(html)); ``` #### Ruby ```ruby require "base64" html = "

Hello!

" encoded = Base64.strict_encode64(html) ``` #### PHP ```php $html = "

Hello!

"; $encoded = base64_encode($html); ``` #### Go ```go import "encoding/base64" html := "

Hello!

" encoded := base64.StdEncoding.EncodeToString([]byte(html)) ``` #### Java ```java import java.util.Base64; String html = "

Hello!

"; String encoded = Base64.getEncoder().encodeToString(html.getBytes("UTF-8")); ``` > **Tip:** If you are using [VS Code](https://code.visualstudio.com/), the [vscode-base64](https://marketplace.visualstudio.com/items?itemName=adamhartford.vscode-base64) extension lets you encode and decode HTML interactively while developing. ## Basic Examples ### HTML to PDF ```shell curl --request POST \ --url https://api.cloudlayer.io/v2/html/pdf \ --header 'Content-Type: application/json' \ --header 'x-api-key: ' \ --data '{ "html": "PGh0bWw+PGJvZHk+PGgxPkhlbGxvIHdvcmxkITwvaDE+PC9ib2R5PjwvaHRtbD4=", "async": false }' ``` ### HTML to Image ```shell curl --request POST \ --url https://api.cloudlayer.io/v2/html/image \ --header 'Content-Type: application/json' \ --header 'x-api-key: ' \ --data '{ "html": "PGh0bWw+PGJvZHk+PGgxPkhlbGxvIHdvcmxkITwvaDE+PC9ib2R5PjwvaHRtbD4=", "async": false }' ``` ### With Viewport Options ```json { "html": "PGh0bWw+PGJvZHk+PGgxPkhlbGxvIHdvcmxkITwvaDE+PC9ib2R5PjwvaHRtbD4=", "viewPort": { "width": 800, "height": 600 } } ``` ## Embedding Images For fully self-contained HTML documents, you can embed images as [data URIs](https://developer.mozilla.org/en-US/docs/Web/HTTP/Basics_of_HTTP/Data_URIs) instead of referencing external URLs. ### Data URI Format ```html ``` Supported formats include `png`, `jpeg`, `gif`, `svg+xml`, and `webp`. ### Example: Embedded Logo ```html
cloudlayer.io
``` ### How to Generate Data URIs #### JavaScript ```javascript const fs = require("fs"); const imageBuffer = fs.readFileSync("logo.png"); const base64 = imageBuffer.toString("base64"); const dataUri = `data:image/png;base64,${base64}`; ``` #### Python ```python import base64 with open("logo.png", "rb") as f: base64_data = base64.b64encode(f.read()).decode("utf-8") data_uri = f"data:image/png;base64,{base64_data}" ``` > **Note:** Having a base64-encoded image embedded inside a base64-encoded HTML string is perfectly valid. The outer encoding is for the JSON transport; the inner encoding is part of the HTML specification. ## Using External Stylesheets Since cloudlayer.io fetches external resources during rendering, you can use any publicly accessible CSS framework, font library, or stylesheet. ### Tailwind CSS ```html

Order Confirmation

Thank you for your purchase.

``` ### Bootstrap ```html
Report Summary

Generated on 2024-01-15

``` ### Google Fonts ```html

Beautiful Typography

Using Google Fonts in your documents.

``` ## Complex Layouts ### CSS Grid ```html

Total Revenue

$45,231

Orders

1,247

Customers

892

Monthly Sales Chart

``` ### Flexbox ```html
Invoice #INV-001
January 15, 2024

From

Acme Inc.

123 Business St.

Bill To

Jane Smith

456 Customer Ave.

``` ## Practical Examples ### Generating an Invoice PDF from HTML A complete example that generates a styled invoice PDF using JavaScript. ```javascript const html = `

Acme Inc.

123 Business Street

New York, NY 10001

INVOICE

#INV-2024-001

January 15, 2024

Item Qty Price Total
Web Development 40 $150.00 $6,000.00
UI Design 20 $120.00 $2,400.00
Subtotal $8,400.00
Tax (8%) $672.00
Total $9,072.00
`; const encoded = Buffer.from(html).toString("base64"); const response = await fetch("https://api.cloudlayer.io/v2/html/pdf", { method: "POST", headers: { "Content-Type": "application/json", "x-api-key": "", }, body: JSON.stringify({ html: encoded, format: "letter", printBackground: true, margin: { top: "0.5in", bottom: "0.5in", left: "0.5in", right: "0.5in", }, async: false, }), }); const result = await response.json(); console.log("PDF URL:", result.assetUrl); ``` ### Creating a Social Media Image Generate an Open Graph or social media card image from HTML. ```python import base64 import requests html = """

Automate Your Document Generation

PDFs, images, and more -- powered by cloudlayer.io

""" encoded = base64.b64encode(html.encode("utf-8")).decode("utf-8") response = requests.post( "https://api.cloudlayer.io/v2/html/image", headers={ "Content-Type": "application/json", "x-api-key": "", }, json={ "html": encoded, "viewPort": { "width": 1200, "height": 630, }, "async": False, }, ) result = response.json() print("Image URL:", result["assetUrl"]) ``` ## PDF-Specific Options When converting HTML to PDF, you can control the output with these common options: | Parameter | Type | Default | Description | |--------------------|-------------------|---------------|----------------------------------------------------| | `format` | `string` | none | Paper format: `letter`, `legal`, `a4`, etc. | | `landscape` | `boolean` | `false` | Use landscape orientation | | `printBackground` | `boolean` | `false` | Include background colors and images | | `margin` | `object` | `0.4in` | Page margins (`top`, `bottom`, `left`, `right`) | | `pageRanges` | `string` | all pages | Which pages to include (e.g., `"1-3, 5"`) | | `scale` | `number` | `1` | Scale factor (0.1 to 2) | | `preferCSSPageSize`| `boolean` | `false` | Use CSS `@page` size instead of format | ### Paper Formats | Format | Dimensions | |-----------|------------------| | `letter` | 8.5in x 11in | | `legal` | 8.5in x 14in | | `tabloid` | 11in x 17in | | `ledger` | 17in x 11in | | `a0` | 33.1in x 46.8in | | `a1` | 23.4in x 33.1in | | `a2` | 16.54in x 23.4in | | `a3` | 11.7in x 16.54in | | `a4` | 8.27in x 11.7in | | `a5` | 5.83in x 8.27in | | `a6` | 4.13in x 5.83in | ## Tips - **Always base64-encode your HTML** before placing it in the JSON payload. Raw HTML in JSON leads to parsing errors. - **Use `printBackground: true`** if your HTML uses background colors or images, Chrome's PDF renderer omits backgrounds by default. - **External resources are fetched at render time.** Any publicly accessible image, font, or stylesheet URL will be loaded. Private or localhost URLs will not work. - **For large HTML documents**, consider using the multipart form upload with the template endpoint instead of base64-encoding everything into a single JSON string. - **Test locally first.** Open your HTML in a browser, use Ctrl+P (print), and verify it looks correct before sending it to the API. ### Working with URLs Source: https://cloudlayer.io/docs/url-examples/ # Working with URLs Convert any publicly accessible web page to a PDF or image by providing its URL. This is the simplest way to use cloudlayer.io, just pass a URL and get back a document. ## Simple URL Capture At its most basic, only a `url` parameter is required. ### JSON Payload ```json { "url": "https://example.com" } ``` ### cURL ```shell curl --request POST \ --url https://api.cloudlayer.io/v2/url/pdf \ --header 'Content-Type: application/json' \ --header 'x-api-key: ' \ --data '{ "url": "https://example.com", "async": false }' ``` ### JavaScript ```javascript const response = await fetch("https://api.cloudlayer.io/v2/url/pdf", { method: "POST", headers: { "Content-Type": "application/json", "x-api-key": "", }, body: JSON.stringify({ url: "https://example.com", async: false, }), }); const result = await response.json(); console.log("PDF URL:", result.assetUrl); ``` ### Python ```python import requests response = requests.post( "https://api.cloudlayer.io/v2/url/pdf", headers={ "Content-Type": "application/json", "x-api-key": "", }, json={ "url": "https://example.com", "async": False, }, ) result = response.json() print("PDF URL:", result["assetUrl"]) ``` ### URL to Image Use the `/url/image` endpoint instead: ```shell curl --request POST \ --url https://api.cloudlayer.io/v2/url/image \ --header 'Content-Type: application/json' \ --header 'x-api-key: ' \ --data '{ "url": "https://example.com", "async": false }' ``` ### GET Request (Simple) For quick captures with minimal options, use the GET endpoint: ```shell curl --request GET \ --url 'https://api.cloudlayer.io/v2/url/pdf?url=https://example.com&timeout=30000' \ --header 'x-api-key: ' ``` The GET endpoint only supports two parameters: `url` (required) and `timeout` (optional). Use the POST endpoint for all other options. ## AutoScroll for Lazy-Loaded Content Many modern websites use lazy loading, images and content only load when the user scrolls to them. The `autoScroll` option scrolls the page from top to bottom before capture, forcing all lazy-loaded content to load. ### JSON Payload ```json { "url": "https://apple.com", "autoScroll": true } ``` ### cURL ```shell curl --request POST \ --url https://api.cloudlayer.io/v2/url/image \ --header 'Content-Type: application/json' \ --header 'x-api-key: ' \ --data '{ "url": "https://apple.com", "autoScroll": true, "async": false }' ``` ### JavaScript ```javascript const response = await fetch("https://api.cloudlayer.io/v2/url/image", { method: "POST", headers: { "Content-Type": "application/json", "x-api-key": "", }, body: JSON.stringify({ url: "https://apple.com", autoScroll: true, async: false, }), }); const result = await response.json(); console.log("Image URL:", result.assetUrl); ``` ### Python ```python import requests response = requests.post( "https://api.cloudlayer.io/v2/url/image", headers={ "Content-Type": "application/json", "x-api-key": "", }, json={ "url": "https://apple.com", "autoScroll": True, "async": False, }, ) result = response.json() print("Image URL:", result["assetUrl"]) ``` ## Viewport Configuration Control the browser viewport dimensions used for rendering. This affects how the page layout responds to different screen sizes. ### Desktop Viewport ```json { "url": "https://example.com", "viewPort": { "width": 1920, "height": 1080, "deviceScaleFactor": 2 } } ``` ### Mobile Viewport ```json { "url": "https://example.com", "viewPort": { "width": 375, "height": 812, "isMobile": true, "hasTouch": true, "deviceScaleFactor": 3 } } ``` ### Tablet Viewport ```json { "url": "https://example.com", "viewPort": { "width": 768, "height": 1024, "isMobile": true, "hasTouch": true, "deviceScaleFactor": 2 } } ``` ### Viewport Parameters | Parameter | Type | Default | Description | |---------------------|-----------|---------|------------------------------------------------| | `width` | `number` | none | Page width in pixels (required) | | `height` | `number` | none | Page height in pixels (required) | | `deviceScaleFactor` | `number` | `1` | Device pixel ratio (2 for Retina-quality) | | `isMobile` | `boolean` | `false` | Emulate a mobile device (affects meta viewport)| | `hasTouch` | `boolean` | `false` | Enable touch event support | | `isLandscape` | `boolean` | `false` | Use landscape orientation | ## Waiting for Dynamic Content ### waitForSelector For single-page applications (SPAs) and pages with dynamically loaded content, use `waitForSelector` to wait for a specific element before capturing. **Wait for an element to appear:** ```json { "url": "https://app.example.com/dashboard", "waitForSelector": { "selector": "#chart-container" } } ``` **Wait for an element to be visible:** ```json { "url": "https://app.example.com/dashboard", "waitForSelector": { "selector": "#chart-container", "options": { "visible": true, "timeout": 15000 } } } ``` **Wait for a loading spinner to disappear:** ```json { "url": "https://app.example.com/report", "waitForSelector": { "selector": ".loading-spinner", "options": { "hidden": true, "timeout": 30000 } } } ``` ### waitForSelector Options | Parameter | Type | Default | Description | |------------|-----------|---------|------------------------------------------------------------| | `selector` | `string` | none | CSS selector of the element to wait for | | `visible` | `boolean` | `false` | Wait for the element to be visible (not just in the DOM) | | `hidden` | `boolean` | `false` | Wait for the element to be hidden or removed | | `timeout` | `number` | `30000` | Maximum wait time in milliseconds | ### waitUntil Control when the Chrome renderer considers the page "loaded" and begins conversion. ```json { "url": "https://example.com", "waitUntil": "networkidle0" } ``` | Value | Description | |--------------------|----------------------------------------------------------------------| | `load` | Wait for the `load` event | | `domcontentloaded` | Wait for the `DOMContentLoaded` event | | `networkidle0` | No network connections for at least 500ms (strictest) | | `networkidle2` | No more than 2 network connections for at least 500ms (default) | ### delay Add a fixed delay (in milliseconds) after the page loads and before conversion begins. Useful when JavaScript-rendered content needs extra time. ```json { "url": "https://example.com", "waitUntil": "networkidle0", "delay": 3000 } ``` ## Batch URL Processing Convert multiple URLs into a single combined PDF using the `batch` parameter. Each URL is converted to PDF individually, then all pages are merged into one document. ### JSON Payload ```json { "batch": { "urls": [ "https://example.com/page-1", "https://example.com/page-2", "https://example.com/page-3" ] } } ``` > **Note:** The `batch` parameter replaces the `url` parameter, do not use both. ### cURL ```shell curl --request POST \ --url https://api.cloudlayer.io/v2/url/pdf \ --header 'Content-Type: application/json' \ --header 'x-api-key: ' \ --data '{ "batch": { "urls": [ "https://example.com/page-1", "https://example.com/page-2", "https://example.com/page-3" ] }, "async": false }' ``` ### JavaScript ```javascript const response = await fetch("https://api.cloudlayer.io/v2/url/pdf", { method: "POST", headers: { "Content-Type": "application/json", "x-api-key": "", }, body: JSON.stringify({ batch: { urls: [ "https://example.com/page-1", "https://example.com/page-2", "https://example.com/page-3", ], }, async: false, }), }); const result = await response.json(); console.log("Combined PDF URL:", result.assetUrl); ``` ### Python ```python import requests response = requests.post( "https://api.cloudlayer.io/v2/url/pdf", headers={ "Content-Type": "application/json", "x-api-key": "", }, json={ "batch": { "urls": [ "https://example.com/page-1", "https://example.com/page-2", "https://example.com/page-3", ], }, "async": False, }, ) result = response.json() print("Combined PDF URL:", result["assetUrl"]) ``` ### Credit Usage Batch processing uses multiple API credits: - **1 credit per URL** in the batch for individual PDF conversion - **1 credit** for the merge operation that combines all PDFs For example, a batch of 3 URLs uses 4 credits total (3 conversions + 1 merge). ## Common Options Reference | Parameter | Type | Default | Description | |--------------------|-------------------|-----------------|----------------------------------------------------------| | `url` | `string` | none | URL to capture (required unless using `batch`) | | `batch` | `object` | none | Object with `urls` array of URLs to convert and merge into one PDF | | `autoScroll` | `boolean` | `false` | Scroll page to load lazy content | | `viewPort` | `object` | none | Browser viewport dimensions | | `waitForSelector` | `object` | none | Wait for a CSS selector before capture | | `waitUntil` | `string` | `networkidle2` | When to consider navigation complete | | `delay` | `number` | `0` | Additional delay in ms after page load | | `timeout` | `number` | `30000` | Maximum time in ms for Chrome to run | | `format` | `string` | none | PDF paper format (e.g., `letter`, `a4`) | | `landscape` | `boolean` | `false` | Landscape orientation | | `printBackground` | `boolean` | `false` | Include CSS backgrounds in PDF | | `margin` | `object` | `0.4in` | PDF page margins | | `scale` | `number` | `1` | Page scale factor (0.1 to 2) | | `timeZone` | `string` | none | Override timezone (e.g., `Europe/Rome`) | | `async` | `boolean` | `true` | Async mode (use webhook) or sync (return result) | | `authentication` | `object` | none | Basic auth credentials (see [Authentication](/docs/authentication-examples/)) | | `cookies` | `array` | none | Session cookies (see [Authentication](/docs/authentication-examples/)) | ## Tips - **Use `autoScroll: true`** for image-heavy sites like portfolios, product pages, and social media feeds. - **Combine `waitForSelector` with `waitUntil: "networkidle0"`** for the most reliable capture of JavaScript-heavy pages. - **Set `printBackground: true`** for PDFs, background colors and images are omitted by default. - **Use a higher `deviceScaleFactor`** (2 or 3) when generating images for high-DPI displays or print. - **For batch processing**, use async mode with webhooks to avoid connection timeouts on large batches. - **If a page requires authentication**, see the [Authentication](/docs/authentication-examples/) examples for Basic Auth and Cookie Auth methods. - **Set `timeZone`** when capturing pages that display time-sensitive content to ensure consistent output across regions. ## Integrations ### MCP Server Setup Source: https://cloudlayer.io/docs/mcp-setup/ # MCP Server Setup Connect cloudlayer.io to your AI assistant so it can generate PDFs, images, and manage documents through natural language. The cloudlayer.io MCP server supports OAuth authentication — authorize once in your browser and your AI gets secure access to your account. **Server URL:** `https://mcp.cloudlayer.io/mcp` --- ## Claude Code The fastest way to get started. Claude Code has built-in OAuth support — it handles authentication automatically. ### Install ```bash claude mcp add --transport http cloudlayer https://mcp.cloudlayer.io/mcp ``` ### Authenticate 1. Run `/mcp` in Claude Code and select **cloudlayer** to authenticate 2. Your browser opens to the cloudlayer.io consent page 3. Sign in (or use your existing session) and click **Authorize** 4. You're connected — start using cloudlayer.io tools in conversation ### Verify Ask Claude Code something like: - "Check my cloudlayer account status" - "Generate a PDF from this HTML: `

Hello World

`" - "What templates are available for invoices?" ### Remove ```bash claude mcp remove cloudlayer ``` --- ## Claude Desktop 1. Open **Settings → Connectors** 2. Click **Add custom connector** at the bottom 3. Paste the server URL: `https://mcp.cloudlayer.io/mcp` 4. Click **Add** Claude Desktop handles OAuth automatically — it opens your browser to authorize when you first use a cloudlayer.io tool. Sign in and click **Authorize**. > **Note:** Remote MCP servers must be added via Settings → Connectors. The `claude_desktop_config.json` file is only for local servers. --- ## Cursor ### Option 1: Settings UI 1. Open Settings: **Cmd+,** (Mac) or **Ctrl+,** (Windows) 2. Navigate to **Features → MCP** 3. Click **+ Add New MCP Server** 4. Enter name `cloudlayer` and URL `https://mcp.cloudlayer.io/mcp` ### Option 2: Config File Add to `.cursor/mcp.json` in your project root (or `~/.cursor/mcp.json` for global): ```json { "cloudlayer": { "type": "http", "url": "https://mcp.cloudlayer.io/mcp" } } ``` > **Note:** Cursor has a limit of ~40 active tools across all MCP servers. cloudlayer.io registers 20 tools, so keep this in mind if you have other MCP servers configured. --- ## Windsurf ### Option 1: Settings UI 1. Open **Windsurf → Settings → Advanced Settings** (or use Command Palette: **Open Windsurf Settings Page**) 2. Scroll to the **Cascade** section 3. Add a new MCP server ### Option 2: Config File Edit `~/.codeium/windsurf/mcp_config.json`: ```json { "mcpServers": { "cloudlayer": { "type": "http", "url": "https://mcp.cloudlayer.io/mcp" } } } ``` Windsurf automatically reloads the config when you save. --- ## VS Code + GitHub Copilot Requires VS Code 1.99+ with GitHub Copilot. 1. Open Settings: **Cmd+,** (Mac) or **Ctrl+,** (Windows) 2. Search for **MCP** 3. Click **Add MCP Server** 4. Enter name `cloudlayer` and URL `https://mcp.cloudlayer.io/mcp` Or add to `.vscode/settings.json`: ```json { "mcp": { "servers": { "cloudlayer": { "type": "http", "url": "https://mcp.cloudlayer.io/mcp" } } } } ``` --- ## Cline ### Option 1: Config UI 1. Click the **MCP Servers** icon in Cline's top navigation 2. Select the **Configure** tab 3. Click **Configure MCP Servers** 4. Add the cloudlayer.io server ### Option 2: Config File Edit the Cline MCP settings file: - **macOS:** `~/Library/Application Support/Code/User/globalStorage/saoudrizwan.claude-dev/settings/cline_mcp_settings.json` - **Windows:** `%APPDATA%/Code/User/globalStorage/saoudrizwan.claude-dev/settings/cline_mcp_settings.json` - **Linux:** `~/.config/Code/User/globalStorage/saoudrizwan.claude-dev/settings/cline_mcp_settings.json` ```json { "mcpServers": { "cloudlayer": { "type": "http", "url": "https://mcp.cloudlayer.io/mcp" } } } ``` --- ## Available Tools Once connected, your AI assistant gets access to these tools: ### Document Generation - **html_to_pdf** / **html_to_image** — Convert HTML to PDF or image - **url_to_pdf** / **url_to_image** — Capture a web page as PDF or image - **template_to_pdf** / **template_to_image** — Render a template with data - **merge_pdfs** — Merge multiple PDFs ### Account & Resources - **get_account** — View usage and subscription info - **list_jobs** / **get_job** — Track conversion jobs - **list_assets** / **get_asset** — Browse generated documents - **list_storage** / **get_storage** / **create_storage** / **delete_storage** — Manage S3 storage - **list_templates** / **get_template** — Browse the template gallery - **get_status** — Check API status ### Documentation - **get_doc** — Look up cloudlayer.io documentation by topic (e.g., `get_doc "html-to-pdf"`) --- ## Revoking Access Each MCP authorization creates a dedicated API key. To revoke access: 1. Go to [app.cloudlayer.io/settings/api-keys](https://beta-app.cloudlayer.io/settings/api-keys) 2. Find the key created for your MCP connection (most recent) 3. Click **Revoke** The AI assistant will need to re-authorize next time it tries to use cloudlayer.io tools. --- ## Troubleshooting **"Unauthorized" errors after connecting** Your token may have expired or the API key was revoked. Remove and re-add the MCP server to trigger a fresh authorization. **Connection timeout** Verify the server URL is exactly `https://mcp.cloudlayer.io/mcp` — including the `/mcp` path. **Tools not appearing** Some clients need a restart after adding an MCP server. Try restarting your AI assistant. **Too many tools warning (Cursor)** Cursor limits active tools to ~40. If you have other MCP servers, you may hit this limit. Consider removing unused servers. ### Zapier Integration Source: https://cloudlayer.io/docs/zapier/ # Zapier Integration [Zapier](https://zapier.com) lets you connect cloudlayer.io with thousands of apps so you can automate document generation without writing any code. Create PDFs, images, invoices, certificates, and more, triggered by events in the tools you already use. Visit the [cloudlayer.io Zapier integration page](https://zapier.com/apps/cloudlayerio/integrations) to get started. ## What You Can Do With the cloudlayer.io Zapier integration, you can: - **Generate PDFs automatically** when a new row is added to a spreadsheet - **Create invoices** when a Stripe payment is received - **Produce certificates** when a student completes a course - **Convert web pages to PDF** on a schedule or triggered by an event - **Send generated documents** via email, Slack, or store them in Google Drive, Dropbox, or S3 ## Available Actions The cloudlayer.io Zapier integration provides create actions, searches, and a completed-job trigger: ### Create Actions | Action | Description | |----------------------|----------------------------------------------------------| | Create PDF From URL | Convert a publicly accessible URL to a PDF document | | Create Image From URL | Capture a screenshot of a URL as a PNG, JPEG, or WebP | | Create PDF From HTML | Convert HTML content to a PDF document | | Create Image From HTML | Convert HTML content to an image | | Create PDF From Template | Generate a PDF from a cloudlayer.io template with data | | Create Image From Template | Generate an image from a cloudlayer.io template | | Create Invoice PDF | Generate an invoice from a predefined invoice template | | Merge PDFs | Combine multiple PDF files into a single document | ### Search Actions | Search | Description | |----------------------|----------------------------------------------------------| | Find Template | Find a template by name or type | | Find Job | Look up a conversion job by ID | | Find Asset | Look up a generated asset by ID | ### Triggers | Trigger | Description | |----------------------|----------------------------------------------------------| | New Completed Job | Triggers when a document conversion job completes | ### Dynamic Template Fields When you select a template in the Template to PDF or Template to Image actions, cloudlayer.io automatically generates input fields based on the template's data schema. Instead of writing raw JSON, you can fill in individual form fields directly in the Zapier editor. ## Common Workflows ### Google Sheets to Invoice PDF Automatically generate a PDF invoice whenever a new row is added to a Google Sheet. **Trigger:** New Spreadsheet Row in Google Sheets **Action:** Template to PDF in cloudlayer.io 1. A new row is added to your "Orders" spreadsheet 2. Zapier maps the spreadsheet columns (customer name, items, amounts) to the invoice template data fields 3. cloudlayer.io generates a professional PDF invoice 4. Optionally, a follow-up step emails the PDF to the customer via Gmail or SendGrid ### Stripe Payment to Invoice Generate and deliver an invoice PDF when a Stripe payment succeeds. **Trigger:** New Payment in Stripe **Action:** Template to PDF in cloudlayer.io 1. A payment is completed in Stripe 2. Zapier extracts the payment details (amount, customer info, description) 3. cloudlayer.io generates an invoice PDF using your branded template 4. The invoice is stored in Google Drive and/or emailed to the customer ### Form Submission to Certificate Create a certificate of completion when someone submits a form. **Trigger:** New Form Submission in Typeform / Google Forms / JotForm **Action:** Template to PDF in cloudlayer.io 1. A student submits a course completion form 2. Zapier maps the form fields (name, course, date) to the certificate template 3. cloudlayer.io generates a personalized certificate PDF 4. The certificate is emailed to the student and saved to Dropbox ### Scheduled Website Archival Capture a website as a PDF on a recurring schedule. **Trigger:** Schedule by Zapier (daily, weekly, etc.) **Action:** URL to PDF in cloudlayer.io 1. Zapier triggers on your chosen schedule 2. cloudlayer.io converts the target URL to PDF 3. The PDF is stored in Google Drive or S3 for archival ## Setup Guide ### Step 1: Create a Zapier Account If you do not have one already, sign up at [zapier.com](https://zapier.com). ### Step 2: Get Your cloudlayer.io API Key 1. Log in to your [cloudlayer.io dashboard](https://beta-app.cloudlayer.io). 2. Navigate to **Settings** > **API Keys**. 3. Copy your API key. If you do not have one, create a new key. ### Step 3: Create a New Zap 1. In Zapier, click **Create Zap** (or **Make a Zap**). 2. **Choose your trigger app**, this is the app that starts the workflow (e.g., Google Sheets, Stripe, Typeform). 3. Configure the trigger event and connect your account. 4. Test the trigger to make sure Zapier can pull in sample data. ### Step 4: Add the cloudlayer.io Action 1. For the action step, search for **cloudlayer.io**. 2. Choose the action you want (e.g., Template to PDF, URL to PDF). 3. Connect your cloudlayer.io account by entering your API key when prompted. 4. Configure the action fields: - **For Template to PDF/Image:** Select or enter your `templateId`, then map data fields from your trigger to the template's data properties. - **For URL to PDF/Image:** Map the URL field from your trigger, or enter a static URL. - **For HTML to PDF/Image:** Provide the HTML content (Zapier will handle the base64 encoding). ### Step 5: Test and Activate 1. Click **Test** to run the Zap with sample data and verify the output. 2. Review the generated document to ensure it looks correct. 3. Turn on the Zap to activate it. ## Mapping Data Fields When using Template to PDF or Template to Image actions, you need to map data from your trigger to the template's expected data fields. **Example:** Mapping Google Sheets columns to an invoice template: | Sheets Column | Template Data Field | |-------------------|------------------------------| | Customer Name | `bill_to_fullname` | | Customer Address | `bill_to_address1` | | Customer City | `bill_to_city` | | Invoice Number | `invoice_no` | | Item Description | `items[0].title` | | Quantity | `items[0].quantity` | | Unit Price | `items[0].unit_price` | > **Tip:** Use the sample data from the [template gallery](https://cloudlayer.io/templates/pdf/) to understand what data fields each template expects. ## Tips and Best Practices - **Test with sample data first** before activating your Zap on live data. - **Use predefined templates** from the cloudlayer.io gallery for the easiest setup, they have well-documented data schemas. - **Add a delay step** if your trigger fires rapidly (e.g., batch spreadsheet imports) to avoid hitting rate limits. - **Use Zapier's formatter** step to transform data (dates, numbers, text) before sending it to cloudlayer.io. - **Add error handling** with Zapier Paths to handle cases where document generation might fail (e.g., missing required fields). - **Store generated documents** in cloud storage (Google Drive, Dropbox, S3) as a follow-up step for easy access and archival. - **Use filters** to only generate documents when specific conditions are met (e.g., order amount > $100).