POST /v1/render/batch
Submits many renders at once. Every item is queued for the worker — a batch never renders inside
the request — so the call returns 202 immediately with one entry per item.
POST https://api.galleyrender.com/v1/render/batchAuthorization: Bearer glr_sk_…Content-Type: application/jsonRequest
Section titled “Request”| Field | Type | Required | Description |
|---|---|---|---|
renders | array | yes | 1 to 50 render requests. Each item takes exactly the fields of POST /v1/render: template, version, data, format, options, webhook_url, async. |
webhook_url | string | no | Default webhook for every item that does not set its own. |
The ceiling is 50 items (RENDER_MAX_BATCH_ITEMS). A larger array is rejected with
invalid_request before anything is queued.
Response
Section titled “Response”202 Accepted.
| Field | Type | Description |
|---|---|---|
object | string | Always batch. |
id | string | bat_…. Every render created by this call carries it as batch_id. |
count | number | Items submitted, including the ones that failed validation. |
queued | number | Items that were accepted and queued. |
cached | number | Items that hit the cache and came back already finished. |
renders | array | One entry per item, in submission order. |
Each entry in renders is either a render object with an extra index, or an error with
object: "error" and the same index:
{ "index": 0, "object": "render", "id": "rnd_…", "status": "queued", … }{ "index": 1, "object": "error", "error": { "type": "validation_error", … } }Examples
Section titled “Examples”Three invoices, one webhook
Section titled “Three invoices, one webhook” Batch three renders shell
curl -sS https://api.galleyrender.com/v1/render/batch \
-H "Authorization: Bearer $GALLEY_API_KEY" \
-H 'content-type: application/json' \
-d '{
"webhook_url": "https://example.com/hooks/galley",
"renders": [
{ "template": "invoice@1", "format": "pdf",
"data": { "invoice_number": "INV-1042", "issued_on": "2026-09-16",
"seller": { "name": "Galley Render" }, "buyer": { "name": "Acme Robotics" },
"line_items": [{ "description": "September", "quantity": 1, "unit_price": 19 }] } },
{ "template": "invoice@1", "format": "pdf",
"data": { "invoice_number": "INV-1043", "issued_on": "2026-09-16",
"seller": { "name": "Galley Render" }, "buyer": { "name": "Initech" },
"line_items": [{ "description": "September", "quantity": 1, "unit_price": 79 }] } },
{ "template": "invoice@1", "format": "pdf",
"data": { "invoice_number": "INV-1044" } }
]
}' Batch three renders javascript
// Node 22+. No dependencies — `fetch` is built in.
const res = await fetch("https://api.galleyrender.com/v1/render/batch", {
method: "POST",
headers: {
authorization: `Bearer ${process.env.GALLEY_API_KEY}`,
"content-type": "application/json",
},
body: JSON.stringify({
webhook_url: "https://example.com/hooks/galley",
renders: [
{
template: "invoice@1",
format: "pdf",
data: {
invoice_number: "INV-1042",
issued_on: "2026-09-16",
seller: {
name: "Galley Render"
},
buyer: {
name: "Acme Robotics"
},
line_items: [
{
description: "September",
quantity: 1,
unit_price: 19
}
]
}
},
{
template: "invoice@1",
format: "pdf",
data: {
invoice_number: "INV-1043",
issued_on: "2026-09-16",
seller: {
name: "Galley Render"
},
buyer: {
name: "Initech"
},
line_items: [
{
description: "September",
quantity: 1,
unit_price: 79
}
]
}
},
{
template: "invoice@1",
format: "pdf",
data: {
invoice_number: "INV-1044"
}
}
]
}),
});
const batch = await res.json();
if (!res.ok) throw new Error(batch.error.message);
console.log(batch); Batch three renders python
# Python 3.9+. Standard library only.
import json, os, urllib.request
body = json.dumps({
"webhook_url": "https://example.com/hooks/galley",
"renders": [
{
"template": "invoice@1",
"format": "pdf",
"data": {
"invoice_number": "INV-1042",
"issued_on": "2026-09-16",
"seller": {
"name": "Galley Render"
},
"buyer": {
"name": "Acme Robotics"
},
"line_items": [
{
"description": "September",
"quantity": 1,
"unit_price": 19
}
]
}
},
{
"template": "invoice@1",
"format": "pdf",
"data": {
"invoice_number": "INV-1043",
"issued_on": "2026-09-16",
"seller": {
"name": "Galley Render"
},
"buyer": {
"name": "Initech"
},
"line_items": [
{
"description": "September",
"quantity": 1,
"unit_price": 79
}
]
}
},
{
"template": "invoice@1",
"format": "pdf",
"data": {
"invoice_number": "INV-1044"
}
}
]
}).encode()
req = urllib.request.Request(
"https://api.galleyrender.com/v1/render/batch",
data=body,
headers={
"Authorization": f"Bearer {os.environ['GALLEY_API_KEY']}",
"Content-Type": "application/json",
},
)
batch = json.load(urllib.request.urlopen(req))
print(batch) { "object": "batch", "id": "bat_5t8wq2jm9r4k", "count": 3, "queued": 2, "cached": 0, "renders": [ { "index": 0, "object": "render", "id": "rnd_9x2mq7k4t1bd", "status": "queued", "template": "invoice@1", "format": "pdf", "engine": "chromium", "cached": false, "url": null, "expires_at": "2026-10-16T14:20:03.771Z", "page_count": null, "billable_units": null, "byte_size": null, "content_type": "application/pdf", "created_at": "2026-09-16T14:20:03.771Z", "completed_at": null, "error": null, "batch_id": "bat_5t8wq2jm9r4k" }, { "index": 1, "object": "render", "id": "rnd_4kp1vt8n2wq6", "status": "queued", "template": "invoice@1", "format": "pdf", "engine": "chromium", "cached": false, "url": null, "expires_at": "2026-10-16T14:20:03.902Z", "page_count": null, "billable_units": null, "byte_size": null, "content_type": "application/pdf", "created_at": "2026-09-16T14:20:03.902Z", "completed_at": null, "error": null, "batch_id": "bat_5t8wq2jm9r4k" }, { "index": 2, "object": "error", "error": { "type": "validation_error", "message": "The data payload does not match the template schema.", "docs_url": "https://galleyrender.com/docs/errors/validation_error", "errors": [ { "path": "data.issued_on", "message": "data.issued_on is required", "expected": "string (required)", "received": "missing", "example": "2026-09-16" }, { "path": "data.line_items", "message": "data.line_items is required", "expected": "array (required)", "received": "missing", "example": [] } ] } } ]}Fix item 2 from the field errors and resubmit just that one.
Too many items
Section titled “Too many items”curl -sS https://api.galleyrender.com/v1/render/batch \ -H "Authorization: Bearer $GALLEY_API_KEY" \ -H 'content-type: application/json' \ -d "{\"renders\": $(python3 -c 'import json;print(json.dumps([{"template":"invoice@1"}]*51))')}"{ "error": { "type": "invalid_request", "message": "A batch holds at most 50 renders.", "docs_url": "https://galleyrender.com/docs/errors/invalid_request", "details": { "received": 51, "max": 50 } }}Collecting the results
Section titled “Collecting the results”Three ways, in rough order of preference:
- Webhooks. Each item fires its own POST when it finishes. See Webhooks.
- Poll by id.
GET /v1/renders/:idfor each id you got back. - List recent renders.
GET /v1/renders?limit=100and match onbatch_id. There is noGET /v1/batches/:idendpoint.
Errors
Section titled “Errors”| Type | Status | When |
|---|---|---|
invalid_request | 400 | Body is not JSON, renders is missing, empty or not an array, or it holds more than 50 items. |
authentication_error | 401 | Missing, unknown or revoked key. |
Everything else — validation_error, not_found, quota_exceeded, spend_cap_exceeded,
conflict — arrives inline, per item, never as the status of the whole call.