Template API
The Template API provides REST endpoints for managing reusable PDF templates from internal tools, CI workflows, or AI coding agents without opening the Dashboard Designer.
The Template API and Conversion API use different credentials: use a Personal Access Token to manage templates and a Conversion API key to generate PDFs from published templates.
| API | Authentication header | Purpose |
|---|---|---|
| Conversion API | API-KEY | Generate PDFs from HTML, URLs, or published templates. |
| Template API | PERSONAL-ACCESS-TOKEN | Create, list, retrieve, update, validate, preview, compare, and publish templates. |
Base URL
Send Template API requests to:
https://api.pdfbolt.com/v1/templates
Authentication
All Template API endpoints except GET /v1/templates/contract require a Personal Access Token. Create one on the API Credentials page in the Dashboard, under Personal Access Tokens.
Send the token in the PERSONAL-ACCESS-TOKEN header with every protected request:
PERSONAL-ACCESS-TOKEN: <YOUR_PERSONAL_ACCESS_TOKEN>
Each Personal Access Token belongs to the user who created it, not to a team. Requests authenticate as that user and can access the same templates the user sees in the Dashboard. In a shared team, this can include templates created by other members.
Anyone with the token can access, modify, and publish your templates. Deactivate it immediately if it may have been compromised.
Personal Access Tokens cannot be restricted to specific templates or operations and do not expire automatically. Preview and diff requests consume conversion credits.
Store tokens in an environment variable or secret manager. Never expose them in client-side code, logs, or source control.
You can have up to five Personal Access Tokens. Deactivation takes effect immediately. Blocked tokens cannot be used or reactivated in the Dashboard, but they still count toward the limit. Contact support to restore access.
How the Template API works
You can complete the full template creation and publishing workflow through the Template API without opening the Dashboard Designer. Start with GET /v1/templates/contract to retrieve the current template rules and supported fields.
Create and publish a template:
- Prepare the template. Create the HTML, add representative sample data, and configure the PDF parameters.
- Validate the template. Check the payload and template syntax before using credits to render a PDF.
- Preview and review. Render the candidate as a PDF and inspect the visual result. Revise and repeat until it is ready.
- Save the draft. Store the reviewed template without changing the version used for production conversions.
- Publish the draft. Make it the active version used by the Conversion API.
- Generate production PDFs. Send the
templateIdand document-specifictemplateDatato the Conversion API.
When updating a published template, use diff during the preview step to compare the candidate with the current production version.
Endpoints overview
The following endpoints cover the complete template management lifecycle:
| Method | Endpoint | Purpose |
|---|---|---|
GET | /v1/templates/contract | Return the versioned Template Contract. |
GET | /v1/templates | List the current team's templates. |
GET | /v1/templates/{templateId} | Return template details, including content, sample data, and PDF parameters. |
POST | /v1/templates/validate | Validate a template payload without rendering a PDF. |
POST | /v1/templates/preview | Render a candidate template as PDF or JSON. |
POST | /v1/templates/{templateId}/diff | Compare a candidate payload with the published version. |
POST | /v1/templates/drafts | Create or update a template draft. |
POST | /v1/templates/{templateId}/publish | Publish the active draft. |
Request fields
Template API request bodies use the fields below. Each endpoint accepts a different subset. See the endpoint reference for the exact request shape.
| Field | Type | Description |
|---|---|---|
templateId | string (UUID) | Identifies an existing template when saving a draft. Omit it to create a new template and its first draft. |
name | string | Template name. Required when creating a new template and optional when updating one. Maximum length: 100 characters. |
description | string | Optional template description. Maximum length: 500 characters. |
templateEngine | string | Template syntax. Currently only HANDLEBARS is supported. Required for validate, preview, and new template creation, but optional for diff and draft updates. |
content | string | Complete Base64-encoded UTF-8 HTML document, including any CSS. Required for validate, preview, diff, and new template creation. Optional when updating an existing template; a supplied value replaces the complete stored document. Maximum decoded size: 2 MB. |
sampleData | object | Representative JSON data used to evaluate the template. Required for validate, preview, diff, and new template creation. Optional when updating an existing template; a supplied object replaces the complete stored object. Maximum JSON size: 2 MB. |
parameters | object | Default PDF parameters saved with a template version. Required for validate, preview, and diff. Optional for template creation and existing-template updates; updates apply it as a shallow patch. Maximum JSON size: 2 MB. |
comment | string | Optional publish comment. Maximum length: 500 characters. |
Template parameters
The parameters object contains the PDF parameters saved with a template version. When creating a new template, you can omit parameters, send an empty object ({}), or provide only selected fields. PDFBolt fills in the missing fields using the defaults shown below and saves the resulting parameters with the template version.
When generating PDFs from the published template, the saved parameters are applied automatically. To change them for a specific PDF, send conversion parameters alongside templateId and templateData. They override the saved parameters for that request only and are not saved to the template.
When validating, previewing, or comparing a candidate, use the same parameters fields. The Template API accepts the fields and values below:
| Field | Accepted values | New template default |
|---|---|---|
format | Letter, Legal, Tabloid, Ledger, A0–A6 | Letter |
landscape | true or false | false |
waitUntil | load, domcontentloaded, networkidle, or commit | load |
printBackground | true or false | true |
displayHeaderFooter | true or false | false |
headerTemplate | Base64-encoded HTML string | Not set |
footerTemplate | Base64-encoded HTML string | Not set |
waitForFunction | JavaScript function | See the default below |
waitForFunctionWhen waitForFunction is omitted while creating a new template, PDFBolt saves this function:
// wait for all fonts and images to be loaded, if loading fails, try waitUntil networkidle
() => { return document.readyState === 'complete' && document.fonts.status === 'loaded' && Array.from(document.images).every(img => img.complete); }
When updating an existing template, parameters is an optional shallow patch. PDFBolt starts with the active draft's parameters, or the published version's parameters when no draft exists. Omitted fields and {} preserve existing values. Set headerTemplate, footerTemplate, or waitForFunction to null to remove that optional value. Every other parameter requires a concrete value.
Endpoint reference
Get Template Contract
Returns the current version of the Template Contract for programmatic template management. It covers authentication, payload fields, HTML/CSS and Handlebars guidance, defaults, workflows, limits, and technical errors. No authentication is required.
Method: GET
https://api.pdfbolt.com/v1/templates/contract
Success Example
- cURL
- Response
curl 'https://api.pdfbolt.com/v1/templates/contract'
HTTP/1.1 200 OK
Content-Type: application/json
The response body contains the complete Template Contract for the current version. Use this endpoint to retrieve the latest Template Contract instead of copying its rules into your integration.
Template Contract and OpenAPI
GET /v1/templates/contract provides PDFBolt-specific rules for creating, rendering, reviewing, and publishing templates.
OpenAPI YAML defines the exact endpoints, required fields, response schemas, and status codes. Use both when building a Template API integration.
List Templates
Returns the current team's templates with details about their published and draft versions. Templates are ordered by the latest version update, newest first.
Method: GET
https://api.pdfbolt.com/v1/templates
Success Example
- cURL
- Response
curl 'https://api.pdfbolt.com/v1/templates' \
-H 'PERSONAL-ACCESS-TOKEN: XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX'
{
"templates": [
{
"id": "8f37b879-46cb-44fc-a5af-c223f5080771",
"name": "Quarterly Report",
"description": "Quarterly performance report",
"templateEngine": "HANDLEBARS",
"hasDraft": true,
"publishedVersion": null,
"draftVersion": {
"id": 383,
"status": "DRAFT",
"versionNumber": 1,
"createdTime": "2026-06-21T09:14:22Z",
"modifiedTime": "2026-06-21T09:18:07Z"
}
},
{
"id": "2b1124e7-7f8d-4fd9-9d0a-4f8cf0c58f98",
"name": "Invoice",
"description": "Monthly invoice template",
"templateEngine": "HANDLEBARS",
"hasDraft": true,
"publishedVersion": {
"id": 381,
"status": "PUBLISHED",
"versionNumber": 3,
"createdTime": "2026-06-20T10:12:34Z",
"modifiedTime": "2026-06-20T10:12:34Z"
},
"draftVersion": {
"id": 382,
"status": "DRAFT",
"versionNumber": 4,
"createdTime": "2026-06-20T11:03:12Z",
"modifiedTime": "2026-06-20T11:08:41Z"
}
}
]
}
Get Template Details
Returns the template details, including its content, sample data, PDF parameters, active draft, and latest published version. If a draft exists, the content, sample data, and PDF parameters come from it. Otherwise, they come from the published version.
Method: GET
https://api.pdfbolt.com/v1/templates/{templateId}
Success Example
- cURL
- Response
curl 'https://api.pdfbolt.com/v1/templates/2b1124e7-7f8d-4fd9-9d0a-4f8cf0c58f98' \
-H 'PERSONAL-ACCESS-TOKEN: XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX'
{
"id": "2b1124e7-7f8d-4fd9-9d0a-4f8cf0c58f98",
"name": "Invoice",
"description": "Monthly invoice template",
"templateEngine": "HANDLEBARS",
"hasDraft": true,
"content": "PCFET0NUWVBFIGh0bWw+PGh0bWw+PGJvZHk+PGgxPkludm9pY2Uge3tpbnZvaWNlTnVtYmVyfX08L2gxPjxwPnt7Y3VzdG9tZXJOYW1lfX08L3A+PC9ib2R5PjwvaHRtbD4=",
"sampleData": {
"invoiceNumber": "INV-1001",
"customerName": "Acme Inc."
},
"parameters": {
"format": "A4",
"landscape": false,
"waitUntil": "load",
"footerTemplate": "PGRpdj48c3BhbiBjbGFzcz0icGFnZU51bWJlciI+PC9zcGFuPjwvZGl2Pg==",
"headerTemplate": "PGRpdj48L2Rpdj4=",
"printBackground": true,
"waitForFunction": "() => document.readyState === 'complete'",
"displayHeaderFooter": true
},
"publishedVersion": {
"id": 381,
"status": "PUBLISHED",
"versionNumber": 3,
"createdTime": "2026-06-20T10:12:34Z",
"modifiedTime": "2026-06-20T10:12:34Z"
},
"draftVersion": {
"id": 382,
"status": "DRAFT",
"versionNumber": 4,
"createdTime": "2026-06-20T11:03:12Z",
"modifiedTime": "2026-06-20T11:08:41Z"
}
}
Validate Template Payload
Validates a template payload without saving it or rendering a PDF. It checks required fields, Base64 content, payload size limits, the template engine, Handlebars syntax and evaluation, and supported PDF parameters. Use it before previewing or saving a draft.
Method: POST
https://api.pdfbolt.com/v1/templates/validate
- Required body fields:
templateEngine,content,sampleData,parameters - Optional body fields: None
Success Example
- Request
- cURL
- Response
{
"templateEngine": "HANDLEBARS",
"content": "PCFkb2N0eXBlIGh0bWw+PGh0bWw+PGJvZHk+PGgxPkludm9pY2Uge3tpbnZvaWNlTnVtYmVyfX08L2gxPjwvYm9keT48L2h0bWw+",
"sampleData": {"invoiceNumber": "INV-1001"},
"parameters": {
"format": "A4",
"waitUntil": "networkidle",
"printBackground": true
}
}
curl 'https://api.pdfbolt.com/v1/templates/validate' \
-H 'PERSONAL-ACCESS-TOKEN: XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX' \
-H 'Content-Type: application/json' \
-d '{
"templateEngine": "HANDLEBARS",
"content": "PCFkb2N0eXBlIGh0bWw+PGh0bWw+PGJvZHk+PGgxPkludm9pY2Uge3tpbnZvaWNlTnVtYmVyfX08L2gxPjwvYm9keT48L2h0bWw+",
"sampleData": {"invoiceNumber": "INV-1001"},
"parameters": {
"format": "A4",
"waitUntil": "networkidle",
"printBackground": true
}
}'
HTTP/1.1 200 OK
Preview Template Payload
Renders a candidate template payload as a PDF without saving it. Set responseFormat=json to receive the PDF as Base64 together with its size. Each successful preview consumes conversion credits.
Method: POST
https://api.pdfbolt.com/v1/templates/preview
- Required body fields:
templateEngine,content,sampleData,parameters - Optional body fields: None
responseFormat | Returns |
|---|---|
pdf (default) | PDF file (application/pdf) |
json | JSON with the PDF encoded as Base64 and its size in MB |
Success Example
- Request
- cURL
- Response
{
"templateEngine": "HANDLEBARS",
"content": "PCFkb2N0eXBlIGh0bWw+PGh0bWw+PGJvZHk+PGgxPkludm9pY2Uge3tpbnZvaWNlTnVtYmVyfX08L2gxPjwvYm9keT48L2h0bWw+",
"sampleData": {"invoiceNumber": "INV-1001"},
"parameters": {"format": "A4", "waitUntil": "networkidle", "printBackground": true}
}
PDF (default)
curl -D headers.txt 'https://api.pdfbolt.com/v1/templates/preview' \
-H 'PERSONAL-ACCESS-TOKEN: XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX' \
-H 'Content-Type: application/json' \
-d '{
"templateEngine": "HANDLEBARS",
"content": "PCFkb2N0eXBlIGh0bWw+PGh0bWw+PGJvZHk+PGgxPkludm9pY2Uge3tpbnZvaWNlTnVtYmVyfX08L2gxPjwvYm9keT48L2h0bWw+",
"sampleData": {"invoiceNumber": "INV-1001"},
"parameters": {"format": "A4", "waitUntil": "networkidle", "printBackground": true}
}' \
-o preview.pdf
JSON
To receive JSON instead, add ?responseFormat=json to the URL:
curl -D headers.txt 'https://api.pdfbolt.com/v1/templates/preview?responseFormat=json' \
-H 'PERSONAL-ACCESS-TOKEN: XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX' \
-H 'Content-Type: application/json' \
-d '{
"templateEngine": "HANDLEBARS",
"content": "PCFkb2N0eXBlIGh0bWw+PGh0bWw+PGJvZHk+PGgxPkludm9pY2Uge3tpbnZvaWNlTnVtYmVyfX08L2gxPjwvYm9keT48L2h0bWw+",
"sampleData": {"invoiceNumber": "INV-1001"},
"parameters": {"format": "A4", "waitUntil": "networkidle", "printBackground": true}
}'
PDF (default)
The response body contains raw PDF bytes:
HTTP/1.1 200 OK
Content-Type: application/pdf
JSON
HTTP/1.1 200 OK
Content-Type: application/json
{
"documentSizeMb": 0.08,
"pdfBase64": "JVBERi0xLjQK..."
}
The x-pdfbolt-conversion-cost response header reports the credits charged for either response format.
Compare a candidate with the published version
The template must already have a published version. This endpoint renders it as before and the candidate payload as after for visual comparison. Conversion credits are charged separately for each PDF that renders successfully.
Method: POST
https://api.pdfbolt.com/v1/templates/{templateId}/diff
- Required body fields:
content,sampleData,parameters - Optional body fields:
templateEngine
Success Example
- Request
- cURL
- Response
{
"content": "PCFkb2N0eXBlIGh0bWw+PGh0bWw+PGJvZHk+PGgxPkludm9pY2Uge3tpbnZvaWNlTnVtYmVyfX08L2gxPjxwPnt7Y3VzdG9tZXJOYW1lfX08L3A+PC9ib2R5PjwvaHRtbD4=",
"sampleData": {"invoiceNumber": "INV-1001", "customerName": "Acme Inc."},
"parameters": {"format": "A4", "waitUntil": "networkidle", "printBackground": true}
}
curl -D headers.txt 'https://api.pdfbolt.com/v1/templates/2b1124e7-7f8d-4fd9-9d0a-4f8cf0c58f98/diff' \
-H 'PERSONAL-ACCESS-TOKEN: XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX' \
-H 'Content-Type: application/json' \
-d '{
"content": "PCFkb2N0eXBlIGh0bWw+PGh0bWw+PGJvZHk+PGgxPkludm9pY2Uge3tpbnZvaWNlTnVtYmVyfX08L2gxPjxwPnt7Y3VzdG9tZXJOYW1lfX08L3A+PC9ib2R5PjwvaHRtbD4=",
"sampleData": {"invoiceNumber": "INV-1001", "customerName": "Acme Inc."},
"parameters": {"format": "A4", "waitUntil": "networkidle", "printBackground": true}
}'
{
"before": {
"documentSizeMb": 0.1,
"pdfBase64": "JVBERi0xLjQK...",
"error": null
},
"after": {
"documentSizeMb": 0.1,
"pdfBase64": "JVBERi0xLjQK...",
"error": null
}
}
The x-pdfbolt-conversion-cost response header contains the total credits charged for successfully rendered PDFs. If either the before or after PDF fails, that object contains an error, and the header includes only the cost of the successful PDF. If both PDFs fail, the header value is 0.
Create or update a template draft
Creates a new template and its first draft when templateId is omitted. When templateId is provided, the endpoint updates only the supplied fields, using the active draft as the base or the published version when no draft exists. The resulting payload is validated without rendering a PDF or consuming conversion credits.
Method: POST
https://api.pdfbolt.com/v1/templates/drafts
Create Draft
Omit templateId to create a new template and its first draft. If parameters is omitted or contains only some fields, PDFBolt fills in the remaining values using the Dashboard Designer defaults.
- Required body fields:
name,templateEngine,content,sampleData - Optional body fields:
description,parameters
- Request
- cURL
- Response
{
"name": "Invoice",
"description": "Monthly invoice template",
"templateEngine": "HANDLEBARS",
"content": "PCFkb2N0eXBlIGh0bWw+PGh0bWw+PGJvZHk+PGgxPkludm9pY2Uge3tpbnZvaWNlTnVtYmVyfX08L2gxPjwvYm9keT48L2h0bWw+",
"sampleData": {"invoiceNumber": "INV-1001"},
"parameters": {"format": "A4", "waitUntil": "networkidle", "printBackground": true}
}
curl 'https://api.pdfbolt.com/v1/templates/drafts' \
-H 'PERSONAL-ACCESS-TOKEN: XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX' \
-H 'Content-Type: application/json' \
-d '{
"name": "Invoice",
"description": "Monthly invoice template",
"templateEngine": "HANDLEBARS",
"content": "PCFkb2N0eXBlIGh0bWw+PGh0bWw+PGJvZHk+PGgxPkludm9pY2Uge3tpbnZvaWNlTnVtYmVyfX08L2gxPjwvYm9keT48L2h0bWw+",
"sampleData": {"invoiceNumber": "INV-1001"},
"parameters": {"format": "A4", "waitUntil": "networkidle", "printBackground": true}
}'
{
"templateId": "93fee603-cb2a-40db-8deb-b2e3cb1eed0f",
"draftVersionId": 2012,
"versionNumber": 1,
"createdTemplate": true
}
Update Draft
Include templateId and at least one field to update. PDFBolt starts from the active draft, or from the published version when no active draft exists:
- Omitted top-level fields preserve their current values.
- Supplied
contentreplaces the complete HTML document. - Supplied
sampleDatareplaces the complete JSON object; it is not deep-merged. - Supplied
parametersis a shallow patch. Omitted parameter fields and{}preserve their current values. nullremovesheaderTemplate,footerTemplate, orwaitForFunction. Other parameter fields require concrete values.description: ""clears the description.
If an active draft exists, it is updated in place and keeps its draftVersionId and versionNumber. If the template has a published version but no active draft, PDFBolt creates a new draft with the next version number.
- Required body fields:
templateIdand at least one field to update - Updatable fields:
name,description,templateEngine,content,sampleData,parameters
Validate, preview, and diff requests remain complete candidate payloads. Partial-update behavior applies only to saving an existing template draft.
- Request
- cURL
- Response
{
"templateId": "93fee603-cb2a-40db-8deb-b2e3cb1eed0f",
"parameters": {"format": "A4"}
}
curl 'https://api.pdfbolt.com/v1/templates/drafts' \
-H 'PERSONAL-ACCESS-TOKEN: XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX' \
-H 'Content-Type: application/json' \
-d '{
"templateId": "93fee603-cb2a-40db-8deb-b2e3cb1eed0f",
"parameters": {"format": "A4"}
}'
{
"templateId": "93fee603-cb2a-40db-8deb-b2e3cb1eed0f",
"draftVersionId": 2012,
"versionNumber": 1,
"createdTemplate": false
}
createdTemplate indicates whether the request created the template itself. It is false whenever templateId is provided.
Publish a template draft
Publishes the active draft as the template's latest published version. Use the published template for PDF conversions by sending its templateId and templateData to a Conversion API endpoint.
A JSON request body is required. Send {} to publish without a comment. If the template has no active draft, the API returns 400 BAD_REQUEST.
Method: POST
https://api.pdfbolt.com/v1/templates/{templateId}/publish
- Required body fields: None (a JSON body is still required and may be empty)
- Optional body fields:
comment(maximum 500 characters)
Success Example
- Request
- cURL
- Response
{
"comment": "Ready for production"
}
curl 'https://api.pdfbolt.com/v1/templates/93fee603-cb2a-40db-8deb-b2e3cb1eed0f/publish' \
-H 'PERSONAL-ACCESS-TOKEN: XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX' \
-H 'Content-Type: application/json' \
-d '{"comment": "Ready for production"}'
{
"templateId": "93fee603-cb2a-40db-8deb-b2e3cb1eed0f",
"publishedVersionId": 2012,
"versionNumber": 1
}
publishedVersionId is the identifier previously assigned to the draft.
Generate PDFs from a published template
Send the published templateId to /v1/direct, /v1/sync, or /v1/async. Pass dynamic values in templateData and authenticate with an API-KEY.
The Conversion API uses the latest published version, even when a newer draft exists. You can add conversion parameters to override the template defaults for one PDF. These overrides are not saved to the template.
Use the published template ID and template data to generate a PDF:
- Request
- cURL
- Response
{
"templateId": "93fee603-cb2a-40db-8deb-b2e3cb1eed0f",
"templateData": {
"invoiceNumber": "INV-1001",
"customerName": "Acme Inc."
}
}
curl 'https://api.pdfbolt.com/v1/direct' \
-H 'API-KEY: XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX' \
-H 'Content-Type: application/json' \
-d '{
"templateId": "93fee603-cb2a-40db-8deb-b2e3cb1eed0f",
"templateData": {
"invoiceNumber": "INV-1001",
"customerName": "Acme Inc."
}
}' \
--output invoice.pdf
A successful request returns 200 OK with raw PDF bytes:
HTTP/1.1 200 OK
Content-Type: application/pdf
x-pdfbolt-conversion-cost: 1
[binary PDF data]
The cURL example saves the response body as invoice.pdf.
See the OpenAPI YAML for the complete request schemas.
Billing and Rate Limits
Within the Template API, only the preview and diff endpoints render PDFs and consume conversion credits. They use the team's plan-based Conversion API limits and appear in Dashboard logs and usage as Preview Conversions. All other Template API operations do not consume conversion credits.
List, get, validate, save draft, and publish use separate fixed per-user management limits. Their 429 responses include Retry‑After. Preview and diff follow the Conversion API retry behavior, while the public contract endpoint is exempt from rate limiting.
See Rate Limits for current limits, response headers, and retry guidance.
Error Handling
Template API request failures use the standard PDFBolt JSON error response:
{
"timestamp": "2026-07-13T12:00:00Z",
"httpErrorCode": 400,
"errorCode": "BAD_REQUEST",
"errorMessage": "Template validation failed: Field 'content' is required. Send a Base64-encoded UTF-8 HTML document as a string."
}
Read errorMessage for the specific cause. See Error Handling for shared error codes and retry guidance, and the OpenAPI YAML for the exact responses supported by each endpoint.
Request-level failures return non-2xx responses for all Template API endpoints. Preview also returns PDF rendering failures as non-2xx responses. Diff behaves differently: after request validation succeeds, it returns HTTP 200 even if one or both PDFs fail to render. Inspect before.error and after.error before treating the comparison as successful.
Next Steps
📄️ PDF Templates
Learn template concepts and Handlebars syntax
📄️ Quick Start Guide
Generate PDFs from published templates
📄️ OpenAPI Reference
Browse endpoint schemas and responses
📄️ Error Handling
Handle API errors, retries, timeouts, and rate-limit responses