Skip to content

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/batch
Authorization: Bearer glr_sk_…
Content-Type: application/json
FieldTypeRequiredDescription
rendersarrayyes1 to 50 render requests. Each item takes exactly the fields of POST /v1/render: template, version, data, format, options, webhook_url, async.
webhook_urlstringnoDefault 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.

202 Accepted.

FieldTypeDescription
objectstringAlways batch.
idstringbat_…. Every render created by this call carries it as batch_id.
countnumberItems submitted, including the ones that failed validation.
queuednumberItems that were accepted and queued.
cachednumberItems that hit the cache and came back already finished.
rendersarrayOne 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",} }
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)
202 Accepted
{
"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.

51 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))')}"
400 Bad Request
{
"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 }
}
}

Three ways, in rough order of preference:

  1. Webhooks. Each item fires its own POST when it finishes. See Webhooks.
  2. Poll by id. GET /v1/renders/:id for each id you got back.
  3. List recent renders. GET /v1/renders?limit=100 and match on batch_id. There is no GET /v1/batches/:id endpoint.
TypeStatusWhen
invalid_request400Body is not JSON, renders is missing, empty or not an array, or it holds more than 50 items.
authentication_error401Missing, 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.