{"openapi":"3.1.0","info":{"title":"jsontoimg Render API","version":"1.0.0","description":"Programmatic image/PDF rendering from design templates.\n\nAuthenticate with `Authorization: Bearer <api_key>` or `X-Api-Key`.\nCreate keys in the dashboard under **Integrations → API keys**.\n\nPrefer webhooks for backends, `GET /renders/{id}/wait` for scripts,\nand `GET /renders/{id}/events` (SSE) for browser UIs.\n","contact":{"name":"jsontoimg","url":"https://jsontoimg.com"}},"servers":[{"url":"https://app.jsontoimg.com","description":"API"}],"tags":[{"name":"Renders","description":"Create and track single render jobs"},{"name":"Batches","description":"Queue multiple renders in one request"},{"name":"Templates","description":"Inspect template layer schemas for API overrides"},{"name":"Webhooks","description":"Outbound webhook payloads delivered by the worker"}],"security":[{"bearerAuth":[]},{"apiKeyAuth":[]}],"paths":{"/api/v1/render":{"post":{"tags":["Renders"],"operationId":"createRender","summary":"Create a render","description":"Enqueues a render and returns immediately with `status: queued` (HTTP 202).\nPass `?wait=60` (max 120) to block until a terminal status within the timeout.\n","parameters":[{"name":"wait","in":"query","required":false,"schema":{"type":"integer","minimum":1,"maximum":120},"description":"Seconds to wait for completion before returning."},{"name":"dryRun","in":"query","required":false,"schema":{"type":"boolean"},"description":"Validate the payload without creating a job or consuming a credit. Same as body `dryRun`."}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CreateRenderRequest"},"examples":{"basic":{"summary":"Basic PNG render","value":{"template":"c3626dec-4d20-403b-8544-4d2438fcffdb","format":"png","scale":1,"layers":{"headline":{"text":"Hello, world"}}}}}}}},"responses":{"200":{"description":"Terminal status when `wait` completes in time, or a dry-run validation result","content":{"application/json":{"schema":{"oneOf":[{"$ref":"#/components/schemas/RenderJob"},{"$ref":"#/components/schemas/DryRunResult"}]}}}},"202":{"description":"Job accepted and queued","content":{"application/json":{"schema":{"$ref":"#/components/schemas/RenderAccepted"}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"402":{"$ref":"#/components/responses/PaymentRequired"},"422":{"$ref":"#/components/responses/UnprocessableEntity"},"429":{"$ref":"#/components/responses/RateLimited"}}}},"/api/v1/img/sign":{"post":{"tags":["Renders"],"operationId":"signImageUrl","summary":"Mint a signed URL-render link","description":"Returns an HMAC-signed `GET /api/v1/img/{template}` URL for CMS / Open Graph `<img>` tags. The fetch itself needs no API key. Pass `jobs` (1–50) to mint many URLs in one call. Minting does not consume a credit; the first unique GET of each URL does. Repeats are cache hits.\n","requestBody":{"required":true,"content":{"application/json":{"schema":{"oneOf":[{"$ref":"#/components/schemas/SignImageRequest"},{"$ref":"#/components/schemas/SignImageBatchRequest"}]}}}},"responses":{"200":{"description":"Signed URL or batch of signed URLs","content":{"application/json":{"schema":{"oneOf":[{"$ref":"#/components/schemas/SignedImageUrl"},{"$ref":"#/components/schemas/SignedImageUrlBatch"}]}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"404":{"$ref":"#/components/responses/NotFound"},"422":{"$ref":"#/components/responses/UnprocessableEntity"},"503":{"$ref":"#/components/responses/QueueUnavailable"}}}},"/api/v1/img/{templateId}":{"get":{"tags":["Renders"],"operationId":"getSignedImage","summary":"Fetch or render a signed image","description":"Unauthenticated. `sig` is HMAC over the template id and query (except `sig`). Optional `exp` is a unix expiry. Other query keys map to layer `text` or variables. First unique payload renders and bills the template owner; later hits stream the stored file.\n","security":[],"parameters":[{"name":"templateId","in":"path","required":true,"schema":{"type":"string","format":"uuid"}},{"name":"sig","in":"query","required":true,"schema":{"type":"string"}},{"name":"exp","in":"query","required":false,"schema":{"type":"string"},"description":"Unix seconds; omit for a non-expiring URL"},{"name":"format","in":"query","required":false,"schema":{"$ref":"#/components/schemas/RenderFormat"}}],"responses":{"200":{"description":"Image or PDF bytes","content":{"image/png":{"schema":{"type":"string","format":"binary"}},"image/jpeg":{"schema":{"type":"string","format":"binary"}},"image/webp":{"schema":{"type":"string","format":"binary"}},"image/avif":{"schema":{"type":"string","format":"binary"}},"application/pdf":{"schema":{"type":"string","format":"binary"}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"402":{"$ref":"#/components/responses/PaymentRequired"},"404":{"$ref":"#/components/responses/NotFound"},"410":{"$ref":"#/components/responses/Gone"},"422":{"$ref":"#/components/responses/UnprocessableEntity"},"503":{"description":"Still rendering; retry after the header delay","headers":{"Retry-After":{"schema":{"type":"integer"}}}}}}},"/api/v1/templates":{"get":{"tags":["Templates"],"operationId":"listTemplates","summary":"List templates","description":"Returns a page of templates in the API key's workspace (default 50, max 100). If `hasMore` is true, request the next page with `offset`. Use names and descriptions to pick a template, then fetch its schema.\n","parameters":[{"name":"project","in":"query","required":false,"schema":{"type":"string","format":"uuid"},"description":"Filter by project ID"},{"name":"limit","in":"query","required":false,"schema":{"type":"integer","minimum":1,"maximum":100,"default":50},"description":"Page size (default 50, max 100)"},{"name":"offset","in":"query","required":false,"schema":{"type":"integer","minimum":0,"default":0},"description":"Number of templates to skip"}],"responses":{"200":{"description":"Template list","content":{"application/json":{"schema":{"$ref":"#/components/schemas/TemplateList"}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"404":{"$ref":"#/components/responses/NotFound"}}}},"/api/v1/templates/{id}/schema":{"get":{"tags":["Templates"],"operationId":"getTemplateSchema","summary":"Get template layer schema","description":"Returns named layers and overridable attribute schemas for a template. Use this to build valid `layers` payloads for render requests.\n","parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string","format":"uuid"},"description":"Template ID"},{"name":"version","in":"query","required":false,"schema":{"type":"integer","minimum":1},"description":"Pin a specific design version; omit for latest"}],"responses":{"200":{"description":"Template schema","content":{"application/json":{"schema":{"$ref":"#/components/schemas/TemplateSchema"}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"404":{"$ref":"#/components/responses/NotFound"},"422":{"$ref":"#/components/responses/UnprocessableEntity"}}}},"/api/v1/renders/{id}":{"get":{"tags":["Renders"],"operationId":"getRender","summary":"Get render status","description":"Poll status as a fallback. Prefer `/wait` or `/events` in production.\nUse exponential backoff (250ms → 2s cap).\n","parameters":[{"$ref":"#/components/parameters/RenderId"}],"responses":{"200":{"description":"Current job status","content":{"application/json":{"schema":{"$ref":"#/components/schemas/RenderJob"}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"404":{"$ref":"#/components/responses/NotFound"},"429":{"$ref":"#/components/responses/RateLimited"}}}},"/api/v1/renders/{id}/wait":{"get":{"tags":["Renders"],"operationId":"waitForRender","summary":"Wait for render completion","description":"Long-poll until the job is `done` or `failed`, or until `timeout` elapses (max 60s).\nReturns 202 with `retryAfterMs` if still in progress — open another wait request.\n","parameters":[{"$ref":"#/components/parameters/RenderId"},{"name":"timeout","in":"query","required":false,"schema":{"type":"integer","minimum":1,"maximum":60,"default":30},"description":"Seconds to hold the connection (default 30, max 60)."}],"responses":{"200":{"description":"Terminal status","content":{"application/json":{"schema":{"$ref":"#/components/schemas/RenderJob"}}}},"202":{"description":"Still queued/processing after timeout","content":{"application/json":{"schema":{"$ref":"#/components/schemas/RenderWaitRetry"}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"404":{"$ref":"#/components/responses/NotFound"}}}},"/api/v1/renders/{id}/events":{"get":{"tags":["Renders"],"operationId":"streamRenderEvents","summary":"Stream render status (SSE)","description":"Server-Sent Events stream of `event: status` until the job is terminal.\nBest for browser clients with a session cookie.\n","parameters":[{"$ref":"#/components/parameters/RenderId"}],"responses":{"200":{"description":"text/event-stream until terminal","content":{"text/event-stream":{"schema":{"type":"string","description":"SSE payload with status events"}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"404":{"$ref":"#/components/responses/NotFound"}}}},"/api/v1/renders/{id}/asset":{"get":{"tags":["Renders"],"operationId":"getRenderAsset","summary":"Download render asset","description":"Streams the binary asset (HTTP 200). Send a session cookie or API key, or a valid `sig` query from `assetUrl` (no key). Signed fetches are long-lived until the file is archived (410). The former path `/api/v1/renders/{id}/image` still works as an alias.\n","parameters":[{"$ref":"#/components/parameters/RenderId"},{"name":"sig","in":"query","required":false,"schema":{"type":"string"},"description":"HMAC signature. When valid, no Authorization header is required."}],"responses":{"200":{"description":"Image or PDF bytes","content":{"image/png":{"schema":{"type":"string","format":"binary"}},"image/jpeg":{"schema":{"type":"string","format":"binary"}},"image/webp":{"schema":{"type":"string","format":"binary"}},"image/avif":{"schema":{"type":"string","format":"binary"}},"application/pdf":{"schema":{"type":"string","format":"binary"}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"404":{"$ref":"#/components/responses/NotFound"},"410":{"$ref":"#/components/responses/Gone"}}}},"/api/v1/renders/batch":{"post":{"tags":["Batches"],"operationId":"createBatch","summary":"Create a render batch","description":"Queue up to 50 renders in one request.","requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CreateBatchRequest"},"examples":{"twoJobs":{"value":{"jobs":[{"template":"c3626dec-4d20-403b-8544-4d2438fcffdb","format":"png","layers":{"headline":{"text":"A"}}},{"template":"c3626dec-4d20-403b-8544-4d2438fcffdb","format":"jpg","layers":{"headline":{"text":"B"}}}]}}}}}},"responses":{"202":{"description":"Batch accepted","content":{"application/json":{"schema":{"$ref":"#/components/schemas/BatchAccepted"}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"402":{"$ref":"#/components/responses/PaymentRequired"},"422":{"$ref":"#/components/responses/UnprocessableEntity"},"429":{"$ref":"#/components/responses/RateLimited"}}}},"/api/v1/renders/batches/{id}":{"get":{"tags":["Batches"],"operationId":"getBatch","summary":"Get batch status","parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string","format":"uuid"}}],"responses":{"200":{"description":"Batch status and child jobs","content":{"application/json":{"schema":{"$ref":"#/components/schemas/BatchStatus"},"examples":{"doneBatch":{"summary":"Batch with completed jobs","value":{"id":"70b7c66e-703b-43f9-996c-111ef8f4b458","status":"done","totals":{"queued":0,"processing":0,"done":2,"failed":0},"totalJobs":2,"jobs":[{"id":"6da902e9-9107-4fa0-aeab-91a8899fc4c4","status":"done","attempts":1,"error":null,"priority":1,"updatedAt":"2026-08-03T09:29:50.816Z","assetUrl":"https://app.jsontoimg.com/api/v1/renders/6da902e9-9107-4fa0-aeab-91a8899fc4c4/asset"},{"id":"76e652ac-5488-48c1-aeb1-d8395b9fdf76","status":"done","attempts":1,"error":null,"priority":1,"updatedAt":"2026-08-03T09:29:51.003Z","assetUrl":"https://app.jsontoimg.com/api/v1/renders/76e652ac-5488-48c1-aeb1-d8395b9fdf76/asset"}],"createdAt":"2026-08-03T09:29:48.564Z","updatedAt":"2026-08-03T09:29:48.596Z"}}}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"404":{"$ref":"#/components/responses/NotFound"}}}}},"webhooks":{"renderCompleted":{"post":{"tags":["Webhooks"],"operationId":"renderCompletedWebhook","summary":"render.completed","description":"Delivered by the worker when a job finishes successfully.\nWhen `RENDER_WEBHOOK_SECRET` is set, verify\n`X-Render-Signature: sha256=<hex>` against the raw JSON body.\n","parameters":[{"name":"X-Render-Signature","in":"header","required":false,"schema":{"type":"string","example":"sha256=abcdef"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/WebhookPayload"}}}},"responses":{"200":{"description":"Acknowledge receipt"}}}},"renderFailed":{"post":{"tags":["Webhooks"],"operationId":"renderFailedWebhook","summary":"render.failed","description":"Delivered when a job fails. Same signature rules as render.completed.","parameters":[{"name":"X-Render-Signature","in":"header","required":false,"schema":{"type":"string"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/WebhookPayload"}}}},"responses":{"200":{"description":"Acknowledge receipt"}}}}},"components":{"securitySchemes":{"bearerAuth":{"type":"http","scheme":"bearer","description":"API key as `Authorization: Bearer <key>`"},"apiKeyAuth":{"type":"apiKey","in":"header","name":"X-Api-Key","description":"API key via `X-Api-Key` header"}},"parameters":{"RenderId":{"name":"id","in":"path","required":true,"schema":{"type":"string","format":"uuid"}}},"responses":{"BadRequest":{"description":"Invalid request","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"Unauthorized":{"description":"Missing or invalid API key / session","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"PaymentRequired":{"description":"Plan quota exceeded","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"NotFound":{"description":"Resource not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"RateLimited":{"description":"Rate limit exceeded. Response includes `Retry-After` (seconds).","headers":{"Retry-After":{"schema":{"type":"integer"},"description":"Seconds until the client may retry"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"UnprocessableEntity":{"description":"Validation failed (invalid layers, duplicate names, unsupported options)","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"QueueUnavailable":{"description":"Queue or signing is unavailable","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"Gone":{"description":"Render file archived after plan retention","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}},"schemas":{"CreateRenderRequest":{"type":"object","required":["template"],"properties":{"template":{"type":"string","format":"uuid","description":"Template ID"},"version":{"oneOf":[{"type":"integer","minimum":1},{"type":"string","pattern":"^[0-9]+$"}],"description":"Pin a specific design version; omit for latest"},"format":{"$ref":"#/components/schemas/RenderFormat"},"scale":{"type":"number","minimum":0.1,"maximum":5,"default":1,"description":"Output scale. Default 1. Max 2 on Free, 3 on Starter, 4 on Pro, 5 on Scale."},"transparent":{"type":"boolean","default":false,"description":"Clear page chrome and emit alpha (Playwright `omitBackground`).\nOnly valid with `png`, `webp`, or `avif`.\n"},"layers":{"$ref":"#/components/schemas/LayerOverrides"},"webhookUrl":{"type":"string","format":"uri","description":"Absolute http(s) URL for completion/failure callbacks"},"webhook_url":{"type":"string","format":"uri","description":"Snake_case alias for webhookUrl"},"variables":{"type":"object","additionalProperties":true,"description":"Immutable JSON for logic-rule conditions (`source: variables`). Omitted or `{}` leaves rules that depend on variables unmatched (except `is_empty`)."},"dryRun":{"type":"boolean","default":false,"description":"Validate without creating a job or consuming a credit. Returns `{ valid: true, ... }`."},"expandVariants":{"type":"object","properties":{"layer":{"type":"string","description":"Named variant-set layer to expand into one job per variant id"}},"required":["layer"],"description":"Batch-only. Fan out this job into one job per variant on the named set. Rejected when the expanded batch would exceed 50 jobs."}}},"SignImageRequest":{"type":"object","required":["template"],"properties":{"template":{"type":"string","format":"uuid"},"version":{"oneOf":[{"type":"integer","minimum":1},{"type":"string","pattern":"^[0-9]+$"}]},"format":{"$ref":"#/components/schemas/RenderFormat"},"scale":{"type":"number","minimum":0.1,"maximum":5,"description":"Output scale. Max 2 on Free, 3 on Starter, 4 on Pro, 5 on Scale."},"transparent":{"type":"boolean"},"layers":{"$ref":"#/components/schemas/LayerOverrides"},"variables":{"type":"object","additionalProperties":true},"expiresIn":{"type":"integer","minimum":1,"description":"Optional lifetime in seconds. Omit for a non-expiring URL."}}},"SignedImageUrl":{"type":"object","required":["url","expiresAt"],"properties":{"url":{"type":"string","format":"uri"},"expiresAt":{"type":"string","format":"date-time","nullable":true}}},"SignImageBatchRequest":{"type":"object","required":["jobs"],"properties":{"jobs":{"type":"array","minItems":1,"maxItems":50,"items":{"$ref":"#/components/schemas/SignImageRequest"}}}},"SignedImageUrlBatch":{"type":"object","required":["urls"],"properties":{"urls":{"type":"array","items":{"$ref":"#/components/schemas/SignedImageUrl"}}}},"DryRunResult":{"type":"object","required":["valid","template","version","format","scale","transparent","layers","variables"],"properties":{"valid":{"type":"boolean","enum":[true]},"template":{"type":"string","format":"uuid"},"version":{"type":"integer"},"format":{"$ref":"#/components/schemas/RenderFormat"},"scale":{"type":"number"},"transparent":{"type":"boolean"},"layers":{"$ref":"#/components/schemas/LayerOverrides"},"variables":{"type":"object","additionalProperties":true}}},"CreateBatchRequest":{"type":"object","required":["jobs"],"properties":{"jobs":{"type":"array","minItems":1,"maxItems":50,"items":{"$ref":"#/components/schemas/CreateRenderRequest"}}}},"LayerOverrides":{"type":"object","additionalProperties":{"type":"object","additionalProperties":true},"description":"Map of layer name → override attributes (e.g. `text`, `color`, image URLs).\nFor `image_url`, use a public `https://…` URL or an Assets path `/api/uploads/files/{id}` (copy from the dashboard Assets library).\nUnknown layer names are rejected.\n"},"RenderFormat":{"type":"string","enum":["png","jpg","pdf","webp","avif"],"default":"png","description":"`jpeg` is accepted as an alias of `jpg`; stored and returned as `jpg`."},"TemplateList":{"type":"object","required":["templates","limit","offset","hasMore"],"properties":{"templates":{"type":"array","items":{"$ref":"#/components/schemas/TemplateListItem"}},"limit":{"type":"integer"},"offset":{"type":"integer"},"hasMore":{"type":"boolean"}}},"TemplateListItem":{"type":"object","required":["id","name","description","projectId","projectName","updatedAt"],"properties":{"id":{"type":"string","format":"uuid"},"name":{"type":"string"},"description":{"type":"string","nullable":true},"projectId":{"type":"string","format":"uuid","nullable":true},"projectName":{"type":"string","nullable":true},"updatedAt":{"type":"string","format":"date-time"}}},"TemplateSchema":{"type":"object","required":["template","version","layers"],"properties":{"template":{"type":"string","format":"uuid"},"name":{"type":"string"},"description":{"type":"string","nullable":true,"description":"Description of the selected template version"},"version":{"type":"integer"},"layers":{"type":"array","items":{"$ref":"#/components/schemas/NamedLayerSummary"}},"columns":{"type":"array","description":"Flattened API fields for spreadsheet import, such as headline.text.","items":{"type":"object","required":["header","layer","attribute","type","required","sample"],"properties":{"header":{"type":"string"},"layer":{"type":"string"},"attribute":{"type":"string"},"type":{"type":"string"},"required":{"type":"boolean"},"sample":{"type":"string"}}}}}},"NamedLayerSummary":{"type":"object","required":["id","parentId","childIds","pageIndex","pageName","layerType","layerName","attributes"],"properties":{"id":{"type":"string"},"parentId":{"type":"string","nullable":true},"childIds":{"type":"array","items":{"type":"string"}},"pageIndex":{"type":"integer"},"pageName":{"type":"string"},"layerType":{"type":"string"},"layerName":{"type":"string"},"attributes":{"type":"array","items":{"type":"object","properties":{"key":{"type":"string"},"label":{"type":"string"},"type":{"type":"string"},"value":{},"required":{"type":"boolean","description":"When true, render requests must supply this attribute"}}}}}},"RenderStatus":{"type":"string","enum":["queued","processing","done","failed"]},"RenderAccepted":{"type":"object","required":["id","status","format"],"properties":{"id":{"type":"string","format":"uuid"},"status":{"type":"string","enum":["queued"]},"format":{"$ref":"#/components/schemas/RenderFormat"},"plan":{"type":"string"},"priority":{"type":"integer"}}},"RenderJob":{"type":"object","required":["id","status"],"properties":{"id":{"type":"string","format":"uuid"},"status":{"$ref":"#/components/schemas/RenderStatus"},"format":{"$ref":"#/components/schemas/RenderFormat"},"attempts":{"type":"integer"},"priority":{"type":"integer"},"error":{"type":"string","nullable":true},"webhookDeliveredAt":{"type":"string","format":"date-time","nullable":true},"webhookLastError":{"type":"string","nullable":true},"assetUrl":{"type":"string","nullable":true,"description":"Relative or absolute URL to download the asset when done"},"retryAfterMs":{"type":"integer","description":"Present on wait timeout responses"}}},"RenderWaitRetry":{"allOf":[{"$ref":"#/components/schemas/RenderJob"},{"type":"object","properties":{"retryAfterMs":{"type":"integer"}}}]},"BatchAccepted":{"type":"object","required":["id","jobs"],"properties":{"id":{"type":"string","format":"uuid"},"jobs":{"type":"array","items":{"type":"object","properties":{"id":{"type":"string","format":"uuid"},"status":{"$ref":"#/components/schemas/RenderStatus"}}}}}},"BatchJob":{"type":"object","required":["id","status"],"properties":{"id":{"type":"string","format":"uuid"},"status":{"$ref":"#/components/schemas/RenderStatus"},"attempts":{"type":"integer"},"error":{"type":"string","nullable":true},"priority":{"type":"integer"},"updatedAt":{"type":"string","format":"date-time"},"assetUrl":{"type":"string","nullable":true,"description":"Absolute URL to download the asset when status is done (auth required)"}}},"BatchStatus":{"type":"object","required":["id","status","totals","totalJobs","jobs","createdAt","updatedAt"],"properties":{"id":{"type":"string","format":"uuid"},"status":{"type":"string","description":"Aggregate batch status derived from child jobs"},"totals":{"type":"object","required":["queued","processing","done","failed"],"properties":{"queued":{"type":"integer"},"processing":{"type":"integer"},"done":{"type":"integer"},"failed":{"type":"integer"}}},"totalJobs":{"type":"integer"},"jobs":{"type":"array","items":{"$ref":"#/components/schemas/BatchJob"}},"createdAt":{"type":"string","format":"date-time"},"updatedAt":{"type":"string","format":"date-time"}}},"WebhookPayload":{"type":"object","required":["eventId","event","data"],"properties":{"eventId":{"type":"string","description":"Idempotency key for the delivery"},"event":{"type":"string","enum":["render.completed","render.failed"]},"data":{"type":"object","required":["id","status"],"properties":{"id":{"type":"string","format":"uuid"},"status":{"$ref":"#/components/schemas/RenderStatus"},"assetUrl":{"type":"string","nullable":true},"error":{"type":"string","nullable":true}}}}},"Error":{"type":"object","required":["error","code"],"properties":{"error":{"type":"string","description":"Human-readable error message"},"code":{"type":"string","description":"Machine-readable error code","enum":["UNAUTHORIZED","BAD_REQUEST","NOT_FOUND","VALIDATION_ERROR","DUPLICATE_LAYER_NAMES","PAYMENT_REQUIRED","FORBIDDEN","RATE_LIMITED","QUEUE_UNAVAILABLE"]},"details":{"description":"Optional structured context (validation paths, duplicates, hints)","type":"object","additionalProperties":true},"message":{"type":"string","description":"Deprecated alias; prefer `error`","deprecated":true}}}}}}