{"openapi":"3.1.0","info":{"title":"oJo API","version":"0.1.0","description":"Render images and PDFs from HTML or from stored templates. Every request authenticates with an API key from https://ojo.so/dashboard as a Bearer token. Errors answer JSON with `error: true`, a `message`, and where useful `logs`, a `code` and `retryAfter`, beside the RFC 9457 members `status`, `title` and `detail`. The media type is `application/problem+json` when `Accept` prefers it, else `application/json`. A success answer lists in `warnings` each body field the route does not read. A render spends its credits when it starts, and a render that fails costs nothing: its credits are refunded.","contact":{"name":"oJo","url":"https://ojo.so"}},"externalDocs":{"url":"https://ojo.so/docs"},"servers":[{"url":"https://api.ojo.so"}],"security":[{"apiKey":[]}],"tags":[{"name":"Images"},{"name":"PDFs"},{"name":"Templates"},{"name":"Credits"},{"name":"Renders"}],"x-header-sets":{"x-limited-headers":{"X-Request-ID":{"$ref":"#/components/headers/X-Request-ID"},"X-RateLimit-Limit":{"$ref":"#/components/headers/X-RateLimit-Limit"},"X-RateLimit-Remaining":{"$ref":"#/components/headers/X-RateLimit-Remaining"},"X-RateLimit-Reset":{"$ref":"#/components/headers/X-RateLimit-Reset"},"X-RateLimit-Policy":{"$ref":"#/components/headers/X-RateLimit-Policy"}},"x-render-headers":{"Idempotent-Replayed":{"$ref":"#/components/headers/Idempotent-Replayed"},"X-Request-ID":{"$ref":"#/components/headers/X-Request-ID"},"X-RateLimit-Limit":{"$ref":"#/components/headers/X-RateLimit-Limit"},"X-RateLimit-Remaining":{"$ref":"#/components/headers/X-RateLimit-Remaining"},"X-RateLimit-Reset":{"$ref":"#/components/headers/X-RateLimit-Reset"},"X-RateLimit-Policy":{"$ref":"#/components/headers/X-RateLimit-Policy"}},"x-accepted-headers":{"Preference-Applied":{"$ref":"#/components/headers/Preference-Applied"},"Location":{"$ref":"#/components/headers/Location"},"Retry-After":{"$ref":"#/components/headers/Retry-After"},"X-Request-ID":{"$ref":"#/components/headers/X-Request-ID"},"X-RateLimit-Limit":{"$ref":"#/components/headers/X-RateLimit-Limit"},"X-RateLimit-Remaining":{"$ref":"#/components/headers/X-RateLimit-Remaining"},"X-RateLimit-Reset":{"$ref":"#/components/headers/X-RateLimit-Reset"},"X-RateLimit-Policy":{"$ref":"#/components/headers/X-RateLimit-Policy"}},"x-render-accepted":{"description":"Sent with `Prefer: respond-async`: the render is queued. Poll `Location` for its answer, waiting `Retry-After` seconds between polls.","headers":{"Preference-Applied":{"$ref":"#/components/headers/Preference-Applied"},"Location":{"$ref":"#/components/headers/Location"},"Retry-After":{"$ref":"#/components/headers/Retry-After"},"X-Request-ID":{"$ref":"#/components/headers/X-Request-ID"},"X-RateLimit-Limit":{"$ref":"#/components/headers/X-RateLimit-Limit"},"X-RateLimit-Remaining":{"$ref":"#/components/headers/X-RateLimit-Remaining"},"X-RateLimit-Reset":{"$ref":"#/components/headers/X-RateLimit-Reset"},"X-RateLimit-Policy":{"$ref":"#/components/headers/X-RateLimit-Policy"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/RenderRequest"}}}},"x-retry-headers":{"Retry-After":{"$ref":"#/components/headers/Retry-After"},"X-Request-ID":{"$ref":"#/components/headers/X-Request-ID"},"X-RateLimit-Limit":{"$ref":"#/components/headers/X-RateLimit-Limit"},"X-RateLimit-Remaining":{"$ref":"#/components/headers/X-RateLimit-Remaining"},"X-RateLimit-Reset":{"$ref":"#/components/headers/X-RateLimit-Reset"},"X-RateLimit-Policy":{"$ref":"#/components/headers/X-RateLimit-Policy"}}},"paths":{"/v1/image/template":{"post":{"tags":["Images"],"operationId":"renderImageFromTemplate","summary":"Render an image from a stored template","parameters":[{"$ref":"#/components/parameters/IdempotencyKey"},{"$ref":"#/components/parameters/Prefer"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["templateId"],"properties":{"templateId":{"type":"string","description":"The stored template to render."},"modify":{"$ref":"#/components/schemas/Variables","description":"Values for the template's variables."},"viewportWidth":{"type":"integer","description":"Width in pixels. Default 1280."},"viewportHeight":{"type":"integer","description":"Height in pixels. Default 800."},"transparentBackground":{"type":"boolean","description":"Render without a background colour."},"strict":{"type":"boolean","description":"Fail with 422 instead of rendering when a variable is missing or a resource does not load."}}}}}},"responses":{"200":{"description":"The image was rendered and stored.","headers":{"Idempotent-Replayed":{"$ref":"#/components/headers/Idempotent-Replayed"},"X-Request-ID":{"$ref":"#/components/headers/X-Request-ID"},"X-RateLimit-Limit":{"$ref":"#/components/headers/X-RateLimit-Limit"},"X-RateLimit-Remaining":{"$ref":"#/components/headers/X-RateLimit-Remaining"},"X-RateLimit-Reset":{"$ref":"#/components/headers/X-RateLimit-Reset"},"X-RateLimit-Policy":{"$ref":"#/components/headers/X-RateLimit-Policy"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ImageRender"}}}},"202":{"description":"Sent with `Prefer: respond-async`: the render is queued. Poll `Location` for its answer, waiting `Retry-After` seconds between polls.","headers":{"Preference-Applied":{"$ref":"#/components/headers/Preference-Applied"},"Location":{"$ref":"#/components/headers/Location"},"Retry-After":{"$ref":"#/components/headers/Retry-After"},"X-Request-ID":{"$ref":"#/components/headers/X-Request-ID"},"X-RateLimit-Limit":{"$ref":"#/components/headers/X-RateLimit-Limit"},"X-RateLimit-Remaining":{"$ref":"#/components/headers/X-RateLimit-Remaining"},"X-RateLimit-Reset":{"$ref":"#/components/headers/X-RateLimit-Reset"},"X-RateLimit-Policy":{"$ref":"#/components/headers/X-RateLimit-Policy"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/RenderRequest"}}}},"400":{"$ref":"#/components/responses/RenderBadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"402":{"$ref":"#/components/responses/PaymentRequired"},"403":{"$ref":"#/components/responses/Forbidden"},"409":{"$ref":"#/components/responses/IdempotencyKeyInProgress"},"413":{"$ref":"#/components/responses/PayloadTooLarge"},"415":{"$ref":"#/components/responses/UnsupportedMediaType"},"422":{"$ref":"#/components/responses/RenderUnprocessableEntity"},"429":{"$ref":"#/components/responses/TooManyRequests"},"500":{"$ref":"#/components/responses/InternalServerError"},"503":{"$ref":"#/components/responses/ServiceUnavailable"},"504":{"$ref":"#/components/responses/GatewayTimeout"}}}},"/v1/image/bulk":{"post":{"tags":["Images"],"operationId":"renderImagesInBulk","summary":"Render up to 50 images from stored templates","parameters":[{"$ref":"#/components/parameters/IdempotencyKey"},{"$ref":"#/components/parameters/Prefer"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["templates"],"properties":{"templates":{"type":"array","minItems":1,"items":{"type":"object","required":["templateId"],"description":"Send `modify` or `modifications`, not both.","properties":{"templateId":{"type":"string"},"modifications":{"type":"array","minItems":1,"description":"One image per entry, each the values for the template's variables.","items":{"$ref":"#/components/schemas/Variables"}},"modify":{"description":"The values for one image, as in POST /v1/image/template, or a list of them, as `modifications`.","oneOf":[{"$ref":"#/components/schemas/Variables"},{"type":"array","minItems":1,"items":{"$ref":"#/components/schemas/Variables"}}]},"viewportWidth":{"type":"integer"},"viewportHeight":{"type":"integer"},"transparentBackground":{"type":"boolean"}}}},"defaultOptions":{"type":"object","description":"Applied to every template that does not set its own.","properties":{"viewportWidth":{"type":"integer"},"viewportHeight":{"type":"integer"},"transparentBackground":{"type":"boolean"}}},"strict":{"type":"boolean","description":"Fail with 422 when any variable is missing or a field is unknown; an item whose resource does not load becomes an error."}}}}}},"responses":{"200":{"description":"Every image that rendered, and an error for each that did not.","headers":{"Idempotent-Replayed":{"$ref":"#/components/headers/Idempotent-Replayed"},"X-Request-ID":{"$ref":"#/components/headers/X-Request-ID"},"X-RateLimit-Limit":{"$ref":"#/components/headers/X-RateLimit-Limit"},"X-RateLimit-Remaining":{"$ref":"#/components/headers/X-RateLimit-Remaining"},"X-RateLimit-Reset":{"$ref":"#/components/headers/X-RateLimit-Reset"},"X-RateLimit-Policy":{"$ref":"#/components/headers/X-RateLimit-Policy"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/BulkRender"}}}},"202":{"description":"Sent with `Prefer: respond-async`: the render is queued. Poll `Location` for its answer, waiting `Retry-After` seconds between polls.","headers":{"Preference-Applied":{"$ref":"#/components/headers/Preference-Applied"},"Location":{"$ref":"#/components/headers/Location"},"Retry-After":{"$ref":"#/components/headers/Retry-After"},"X-Request-ID":{"$ref":"#/components/headers/X-Request-ID"},"X-RateLimit-Limit":{"$ref":"#/components/headers/X-RateLimit-Limit"},"X-RateLimit-Remaining":{"$ref":"#/components/headers/X-RateLimit-Remaining"},"X-RateLimit-Reset":{"$ref":"#/components/headers/X-RateLimit-Reset"},"X-RateLimit-Policy":{"$ref":"#/components/headers/X-RateLimit-Policy"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/RenderRequest"}}}},"400":{"$ref":"#/components/responses/RenderBadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"402":{"$ref":"#/components/responses/PaymentRequired"},"403":{"$ref":"#/components/responses/Forbidden"},"409":{"$ref":"#/components/responses/IdempotencyKeyInProgress"},"413":{"$ref":"#/components/responses/PayloadTooLarge"},"415":{"$ref":"#/components/responses/UnsupportedMediaType"},"422":{"$ref":"#/components/responses/RenderUnprocessableEntity"},"429":{"$ref":"#/components/responses/TooManyRequests"},"500":{"$ref":"#/components/responses/InternalServerError"},"503":{"$ref":"#/components/responses/ServiceUnavailable"}}}},"/v1/image/html":{"post":{"tags":["Images"],"operationId":"renderImageFromHtml","summary":"Render an image from HTML","parameters":[{"$ref":"#/components/parameters/IdempotencyKey"},{"$ref":"#/components/parameters/Prefer"},{"name":"x-viewport-width","in":"header","schema":{"type":"integer"},"description":"Width in pixels. Default 1280."},{"name":"x-viewport-height","in":"header","schema":{"type":"integer"},"description":"Height in pixels. Default 800."},{"name":"x-transparent-background","in":"header","schema":{"type":"string","enum":["true","false"]},"description":"`true` renders without a background colour."}],"requestBody":{"required":true,"content":{"text/html":{"schema":{"type":"string","description":"The HTML document to render."}},"application/json":{"schema":{"type":"object","required":["html"],"description":"A value here wins over its header.","properties":{"html":{"type":"string","description":"The HTML document to render."},"viewportWidth":{"type":"integer","description":"Width in pixels. Default 1280."},"viewportHeight":{"type":"integer","description":"Height in pixels. Default 800."},"transparentBackground":{"type":"boolean","description":"`true` renders without a background colour."}}}}}},"responses":{"200":{"description":"The image was rendered and stored.","headers":{"Idempotent-Replayed":{"$ref":"#/components/headers/Idempotent-Replayed"},"X-Request-ID":{"$ref":"#/components/headers/X-Request-ID"},"X-RateLimit-Limit":{"$ref":"#/components/headers/X-RateLimit-Limit"},"X-RateLimit-Remaining":{"$ref":"#/components/headers/X-RateLimit-Remaining"},"X-RateLimit-Reset":{"$ref":"#/components/headers/X-RateLimit-Reset"},"X-RateLimit-Policy":{"$ref":"#/components/headers/X-RateLimit-Policy"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ImageRender"}}}},"202":{"description":"Sent with `Prefer: respond-async`: the render is queued. Poll `Location` for its answer, waiting `Retry-After` seconds between polls.","headers":{"Preference-Applied":{"$ref":"#/components/headers/Preference-Applied"},"Location":{"$ref":"#/components/headers/Location"},"Retry-After":{"$ref":"#/components/headers/Retry-After"},"X-Request-ID":{"$ref":"#/components/headers/X-Request-ID"},"X-RateLimit-Limit":{"$ref":"#/components/headers/X-RateLimit-Limit"},"X-RateLimit-Remaining":{"$ref":"#/components/headers/X-RateLimit-Remaining"},"X-RateLimit-Reset":{"$ref":"#/components/headers/X-RateLimit-Reset"},"X-RateLimit-Policy":{"$ref":"#/components/headers/X-RateLimit-Policy"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/RenderRequest"}}}},"400":{"$ref":"#/components/responses/RenderBadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"402":{"$ref":"#/components/responses/PaymentRequired"},"403":{"$ref":"#/components/responses/Forbidden"},"409":{"$ref":"#/components/responses/IdempotencyKeyInProgress"},"413":{"$ref":"#/components/responses/PayloadTooLarge"},"415":{"$ref":"#/components/responses/UnsupportedMediaType"},"422":{"$ref":"#/components/responses/RenderUnprocessableEntity"},"429":{"$ref":"#/components/responses/TooManyRequests"},"500":{"$ref":"#/components/responses/InternalServerError"},"503":{"$ref":"#/components/responses/ServiceUnavailable"},"504":{"$ref":"#/components/responses/GatewayTimeout"}}}},"/v1/image":{"get":{"tags":["Images"],"operationId":"listImages","summary":"List your images","parameters":[{"$ref":"#/components/parameters/page"},{"$ref":"#/components/parameters/pageSize"},{"$ref":"#/components/parameters/sort"}],"responses":{"200":{"description":"One page of images.","headers":{"X-Request-ID":{"$ref":"#/components/headers/X-Request-ID"},"X-RateLimit-Limit":{"$ref":"#/components/headers/X-RateLimit-Limit"},"X-RateLimit-Remaining":{"$ref":"#/components/headers/X-RateLimit-Remaining"},"X-RateLimit-Reset":{"$ref":"#/components/headers/X-RateLimit-Reset"},"X-RateLimit-Policy":{"$ref":"#/components/headers/X-RateLimit-Policy"}},"content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/Page"},{"properties":{"items":{"type":"array","items":{"$ref":"#/components/schemas/ImageRecord"}}}}]}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"422":{"$ref":"#/components/responses/UnprocessableEntity"},"429":{"$ref":"#/components/responses/TooManyRequests"},"500":{"$ref":"#/components/responses/InternalServerError"},"503":{"$ref":"#/components/responses/ServiceUnavailable"}}},"delete":{"tags":["Images"],"operationId":"deleteImages","summary":"Delete up to 50 images","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["imageIds"],"properties":{"imageIds":{"type":"array","minItems":1,"maxItems":50,"items":{"type":"string"}}}}}}},"responses":{"200":{"description":"At least one image was deleted. `failed` names the rest.","headers":{"X-Request-ID":{"$ref":"#/components/headers/X-Request-ID"},"X-RateLimit-Limit":{"$ref":"#/components/headers/X-RateLimit-Limit"},"X-RateLimit-Remaining":{"$ref":"#/components/headers/X-RateLimit-Remaining"},"X-RateLimit-Reset":{"$ref":"#/components/headers/X-RateLimit-Reset"},"X-RateLimit-Policy":{"$ref":"#/components/headers/X-RateLimit-Policy"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ImageBatchDelete"}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"description":"None of the images was found.","headers":{"X-Request-ID":{"$ref":"#/components/headers/X-Request-ID"},"X-RateLimit-Limit":{"$ref":"#/components/headers/X-RateLimit-Limit"},"X-RateLimit-Remaining":{"$ref":"#/components/headers/X-RateLimit-Remaining"},"X-RateLimit-Reset":{"$ref":"#/components/headers/X-RateLimit-Reset"},"X-RateLimit-Policy":{"$ref":"#/components/headers/X-RateLimit-Policy"}},"content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/Error"},{"$ref":"#/components/schemas/ImageBatchDelete"}]}},"application/problem+json":{"schema":{"allOf":[{"$ref":"#/components/schemas/Error"},{"$ref":"#/components/schemas/ImageBatchDelete"}]}}}},"413":{"$ref":"#/components/responses/PayloadTooLarge"},"415":{"$ref":"#/components/responses/UnsupportedMediaType"},"422":{"$ref":"#/components/responses/UnprocessableEntity"},"429":{"$ref":"#/components/responses/TooManyRequests"},"500":{"description":"No image could be deleted. `failed` names each one and why.","headers":{"X-Request-ID":{"$ref":"#/components/headers/X-Request-ID"},"X-RateLimit-Limit":{"$ref":"#/components/headers/X-RateLimit-Limit"},"X-RateLimit-Remaining":{"$ref":"#/components/headers/X-RateLimit-Remaining"},"X-RateLimit-Reset":{"$ref":"#/components/headers/X-RateLimit-Reset"},"X-RateLimit-Policy":{"$ref":"#/components/headers/X-RateLimit-Policy"}},"content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/Error"},{"$ref":"#/components/schemas/ImageBatchDelete"}]}},"application/problem+json":{"schema":{"allOf":[{"$ref":"#/components/schemas/Error"},{"$ref":"#/components/schemas/ImageBatchDelete"}]}}}},"503":{"$ref":"#/components/responses/ServiceUnavailable"}}}},"/v1/image/{imageId}":{"parameters":[{"name":"imageId","in":"path","required":true,"schema":{"type":"string"}}],"get":{"tags":["Images"],"operationId":"getImage","summary":"Get one image","responses":{"200":{"description":"The image.","headers":{"X-Request-ID":{"$ref":"#/components/headers/X-Request-ID"},"X-RateLimit-Limit":{"$ref":"#/components/headers/X-RateLimit-Limit"},"X-RateLimit-Remaining":{"$ref":"#/components/headers/X-RateLimit-Remaining"},"X-RateLimit-Reset":{"$ref":"#/components/headers/X-RateLimit-Reset"},"X-RateLimit-Policy":{"$ref":"#/components/headers/X-RateLimit-Policy"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ImageRecord"}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"422":{"$ref":"#/components/responses/UnprocessableEntity"},"429":{"$ref":"#/components/responses/TooManyRequests"},"500":{"$ref":"#/components/responses/InternalServerError"},"503":{"$ref":"#/components/responses/ServiceUnavailable"}}},"delete":{"tags":["Images"],"operationId":"deleteImage","summary":"Delete one image","responses":{"200":{"description":"The image was deleted.","headers":{"X-Request-ID":{"$ref":"#/components/headers/X-Request-ID"},"X-RateLimit-Limit":{"$ref":"#/components/headers/X-RateLimit-Limit"},"X-RateLimit-Remaining":{"$ref":"#/components/headers/X-RateLimit-Remaining"},"X-RateLimit-Reset":{"$ref":"#/components/headers/X-RateLimit-Reset"},"X-RateLimit-Policy":{"$ref":"#/components/headers/X-RateLimit-Policy"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Deleted"}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"422":{"$ref":"#/components/responses/UnprocessableEntity"},"429":{"$ref":"#/components/responses/TooManyRequests"},"500":{"$ref":"#/components/responses/InternalServerError"},"503":{"$ref":"#/components/responses/ServiceUnavailable"}}}},"/v1/pdf/html":{"post":{"tags":["PDFs"],"operationId":"renderPdfFromHtml","summary":"Render a PDF from HTML","description":"The page size comes from the document's CSS `@page` rule, else A4. One credit per 20 pages, spent at the start for its documents (each prints at least one page) and the rest once the pages are counted.","parameters":[{"$ref":"#/components/parameters/IdempotencyKey"},{"$ref":"#/components/parameters/Prefer"}],"requestBody":{"required":true,"content":{"text/html":{"schema":{"type":"string","description":"The HTML document to print."}},"application/json":{"schema":{"type":"object","required":["html"],"properties":{"html":{"type":"string","description":"The HTML document to print."}}}}}},"responses":{"200":{"description":"The PDF was rendered and stored.","headers":{"Idempotent-Replayed":{"$ref":"#/components/headers/Idempotent-Replayed"},"X-Request-ID":{"$ref":"#/components/headers/X-Request-ID"},"X-RateLimit-Limit":{"$ref":"#/components/headers/X-RateLimit-Limit"},"X-RateLimit-Remaining":{"$ref":"#/components/headers/X-RateLimit-Remaining"},"X-RateLimit-Reset":{"$ref":"#/components/headers/X-RateLimit-Reset"},"X-RateLimit-Policy":{"$ref":"#/components/headers/X-RateLimit-Policy"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/PdfRender"}}}},"202":{"description":"Sent with `Prefer: respond-async`: the render is queued. Poll `Location` for its answer, waiting `Retry-After` seconds between polls.","headers":{"Preference-Applied":{"$ref":"#/components/headers/Preference-Applied"},"Location":{"$ref":"#/components/headers/Location"},"Retry-After":{"$ref":"#/components/headers/Retry-After"},"X-Request-ID":{"$ref":"#/components/headers/X-Request-ID"},"X-RateLimit-Limit":{"$ref":"#/components/headers/X-RateLimit-Limit"},"X-RateLimit-Remaining":{"$ref":"#/components/headers/X-RateLimit-Remaining"},"X-RateLimit-Reset":{"$ref":"#/components/headers/X-RateLimit-Reset"},"X-RateLimit-Policy":{"$ref":"#/components/headers/X-RateLimit-Policy"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/RenderRequest"}}}},"400":{"$ref":"#/components/responses/RenderBadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"402":{"$ref":"#/components/responses/PaymentRequired"},"403":{"$ref":"#/components/responses/Forbidden"},"409":{"$ref":"#/components/responses/IdempotencyKeyInProgress"},"413":{"$ref":"#/components/responses/PayloadTooLarge"},"415":{"$ref":"#/components/responses/UnsupportedMediaType"},"422":{"$ref":"#/components/responses/RenderUnprocessableEntity"},"429":{"$ref":"#/components/responses/TooManyRequests"},"500":{"$ref":"#/components/responses/InternalServerError"},"503":{"$ref":"#/components/responses/ServiceUnavailable"},"504":{"$ref":"#/components/responses/GatewayTimeout"}}}},"/v1/pdf/template":{"post":{"tags":["PDFs"],"operationId":"renderPdfFromTemplate","summary":"Render a PDF from stored templates","description":"Send `templateId` and `modify` for a one-template PDF, or `pages` for a document of several parts, each a stored template or HTML. One credit per 20 pages, spent at the start for its documents (each prints at least one page) and the rest once the pages are counted.","parameters":[{"$ref":"#/components/parameters/IdempotencyKey"},{"$ref":"#/components/parameters/Prefer"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"templateId":{"type":"string","description":"The stored template to print, or the default template of each entry in `pages`."},"modify":{"$ref":"#/components/schemas/Variables","description":"Values for the template's variables, shared by every entry in `pages`."},"pages":{"type":"array","minItems":1,"description":"The parts of the document, printed in order and merged into one PDF.","items":{"type":"object","properties":{"templateId":{"type":"string"},"html":{"type":"string"},"modify":{"$ref":"#/components/schemas/Variables"}}}},"strict":{"type":"boolean","description":"Fail with 422 instead of rendering when a variable is missing or a resource does not load."}}}}}},"responses":{"200":{"description":"The PDF was rendered and stored.","headers":{"Idempotent-Replayed":{"$ref":"#/components/headers/Idempotent-Replayed"},"X-Request-ID":{"$ref":"#/components/headers/X-Request-ID"},"X-RateLimit-Limit":{"$ref":"#/components/headers/X-RateLimit-Limit"},"X-RateLimit-Remaining":{"$ref":"#/components/headers/X-RateLimit-Remaining"},"X-RateLimit-Reset":{"$ref":"#/components/headers/X-RateLimit-Reset"},"X-RateLimit-Policy":{"$ref":"#/components/headers/X-RateLimit-Policy"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/PdfRender"}}}},"202":{"description":"Sent with `Prefer: respond-async`: the render is queued. Poll `Location` for its answer, waiting `Retry-After` seconds between polls.","headers":{"Preference-Applied":{"$ref":"#/components/headers/Preference-Applied"},"Location":{"$ref":"#/components/headers/Location"},"Retry-After":{"$ref":"#/components/headers/Retry-After"},"X-Request-ID":{"$ref":"#/components/headers/X-Request-ID"},"X-RateLimit-Limit":{"$ref":"#/components/headers/X-RateLimit-Limit"},"X-RateLimit-Remaining":{"$ref":"#/components/headers/X-RateLimit-Remaining"},"X-RateLimit-Reset":{"$ref":"#/components/headers/X-RateLimit-Reset"},"X-RateLimit-Policy":{"$ref":"#/components/headers/X-RateLimit-Policy"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/RenderRequest"}}}},"400":{"$ref":"#/components/responses/RenderBadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"402":{"$ref":"#/components/responses/PaymentRequired"},"403":{"$ref":"#/components/responses/Forbidden"},"409":{"$ref":"#/components/responses/IdempotencyKeyInProgress"},"413":{"$ref":"#/components/responses/PayloadTooLarge"},"415":{"$ref":"#/components/responses/UnsupportedMediaType"},"422":{"$ref":"#/components/responses/RenderUnprocessableEntity"},"429":{"$ref":"#/components/responses/TooManyRequests"},"500":{"$ref":"#/components/responses/InternalServerError"},"503":{"$ref":"#/components/responses/ServiceUnavailable"},"504":{"$ref":"#/components/responses/GatewayTimeout"}}}},"/v1/pdf/preview":{"post":{"tags":["PDFs"],"operationId":"previewPdf","summary":"Preview a PDF without storing or charging it","description":"Answers the printed PDF itself, stamped \"Preview\" on every page. Nothing is stored and no credit is spent.","requestBody":{"required":true,"content":{"text/html":{"schema":{"type":"string","description":"The HTML document to print."}}}},"responses":{"200":{"description":"The stamped PDF.","headers":{"X-Pdf-Pages":{"description":"The number of pages.","schema":{"type":"integer","minimum":1}},"X-Render-Warnings":{"description":"Resources the page could not load, as a percent-encoded JSON array of strings. Absent when there were none.","schema":{"type":"string"}},"X-Request-ID":{"$ref":"#/components/headers/X-Request-ID"},"X-RateLimit-Limit":{"$ref":"#/components/headers/X-RateLimit-Limit"},"X-RateLimit-Remaining":{"$ref":"#/components/headers/X-RateLimit-Remaining"},"X-RateLimit-Reset":{"$ref":"#/components/headers/X-RateLimit-Reset"},"X-RateLimit-Policy":{"$ref":"#/components/headers/X-RateLimit-Policy"}},"content":{"application/pdf":{"schema":{"type":"string","contentMediaType":"application/pdf"}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"413":{"$ref":"#/components/responses/PayloadTooLarge"},"415":{"$ref":"#/components/responses/UnsupportedMediaType"},"422":{"$ref":"#/components/responses/UnprocessableEntity"},"429":{"$ref":"#/components/responses/TooManyRequests"},"500":{"$ref":"#/components/responses/InternalServerError"},"503":{"$ref":"#/components/responses/ServiceUnavailable"},"504":{"$ref":"#/components/responses/GatewayTimeout"}}}},"/v1/pdf":{"get":{"tags":["PDFs"],"operationId":"listPdfs","summary":"List your PDFs","parameters":[{"$ref":"#/components/parameters/page"},{"$ref":"#/components/parameters/pageSize"},{"$ref":"#/components/parameters/sort"}],"responses":{"200":{"description":"One page of PDFs.","headers":{"X-Request-ID":{"$ref":"#/components/headers/X-Request-ID"},"X-RateLimit-Limit":{"$ref":"#/components/headers/X-RateLimit-Limit"},"X-RateLimit-Remaining":{"$ref":"#/components/headers/X-RateLimit-Remaining"},"X-RateLimit-Reset":{"$ref":"#/components/headers/X-RateLimit-Reset"},"X-RateLimit-Policy":{"$ref":"#/components/headers/X-RateLimit-Policy"}},"content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/Page"},{"properties":{"items":{"type":"array","items":{"$ref":"#/components/schemas/PdfRecord"}}}}]}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"422":{"$ref":"#/components/responses/UnprocessableEntity"},"429":{"$ref":"#/components/responses/TooManyRequests"},"500":{"$ref":"#/components/responses/InternalServerError"},"503":{"$ref":"#/components/responses/ServiceUnavailable"}}},"delete":{"tags":["PDFs"],"operationId":"deletePdfs","summary":"Delete up to 50 PDFs","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["pdfIds"],"properties":{"pdfIds":{"type":"array","minItems":1,"maxItems":50,"items":{"type":"string"}}}}}}},"responses":{"200":{"description":"At least one PDF was deleted. `failed` names the rest.","headers":{"X-Request-ID":{"$ref":"#/components/headers/X-Request-ID"},"X-RateLimit-Limit":{"$ref":"#/components/headers/X-RateLimit-Limit"},"X-RateLimit-Remaining":{"$ref":"#/components/headers/X-RateLimit-Remaining"},"X-RateLimit-Reset":{"$ref":"#/components/headers/X-RateLimit-Reset"},"X-RateLimit-Policy":{"$ref":"#/components/headers/X-RateLimit-Policy"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/PdfBatchDelete"}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"description":"None of the PDFs was found.","headers":{"X-Request-ID":{"$ref":"#/components/headers/X-Request-ID"},"X-RateLimit-Limit":{"$ref":"#/components/headers/X-RateLimit-Limit"},"X-RateLimit-Remaining":{"$ref":"#/components/headers/X-RateLimit-Remaining"},"X-RateLimit-Reset":{"$ref":"#/components/headers/X-RateLimit-Reset"},"X-RateLimit-Policy":{"$ref":"#/components/headers/X-RateLimit-Policy"}},"content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/Error"},{"$ref":"#/components/schemas/PdfBatchDelete"}]}},"application/problem+json":{"schema":{"allOf":[{"$ref":"#/components/schemas/Error"},{"$ref":"#/components/schemas/PdfBatchDelete"}]}}}},"413":{"$ref":"#/components/responses/PayloadTooLarge"},"415":{"$ref":"#/components/responses/UnsupportedMediaType"},"422":{"$ref":"#/components/responses/UnprocessableEntity"},"429":{"$ref":"#/components/responses/TooManyRequests"},"500":{"description":"No PDF could be deleted. `failed` names each one and why.","headers":{"X-Request-ID":{"$ref":"#/components/headers/X-Request-ID"},"X-RateLimit-Limit":{"$ref":"#/components/headers/X-RateLimit-Limit"},"X-RateLimit-Remaining":{"$ref":"#/components/headers/X-RateLimit-Remaining"},"X-RateLimit-Reset":{"$ref":"#/components/headers/X-RateLimit-Reset"},"X-RateLimit-Policy":{"$ref":"#/components/headers/X-RateLimit-Policy"}},"content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/Error"},{"$ref":"#/components/schemas/PdfBatchDelete"}]}},"application/problem+json":{"schema":{"allOf":[{"$ref":"#/components/schemas/Error"},{"$ref":"#/components/schemas/PdfBatchDelete"}]}}}},"503":{"$ref":"#/components/responses/ServiceUnavailable"}}}},"/v1/pdf/{pdfId}":{"parameters":[{"name":"pdfId","in":"path","required":true,"schema":{"type":"string"}}],"get":{"tags":["PDFs"],"operationId":"getPdf","summary":"Get one PDF","responses":{"200":{"description":"The PDF.","headers":{"X-Request-ID":{"$ref":"#/components/headers/X-Request-ID"},"X-RateLimit-Limit":{"$ref":"#/components/headers/X-RateLimit-Limit"},"X-RateLimit-Remaining":{"$ref":"#/components/headers/X-RateLimit-Remaining"},"X-RateLimit-Reset":{"$ref":"#/components/headers/X-RateLimit-Reset"},"X-RateLimit-Policy":{"$ref":"#/components/headers/X-RateLimit-Policy"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/PdfRecord"}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"422":{"$ref":"#/components/responses/UnprocessableEntity"},"429":{"$ref":"#/components/responses/TooManyRequests"},"500":{"$ref":"#/components/responses/InternalServerError"},"503":{"$ref":"#/components/responses/ServiceUnavailable"}}},"delete":{"tags":["PDFs"],"operationId":"deletePdf","summary":"Delete one PDF","responses":{"200":{"description":"The PDF was deleted.","headers":{"X-Request-ID":{"$ref":"#/components/headers/X-Request-ID"},"X-RateLimit-Limit":{"$ref":"#/components/headers/X-RateLimit-Limit"},"X-RateLimit-Remaining":{"$ref":"#/components/headers/X-RateLimit-Remaining"},"X-RateLimit-Reset":{"$ref":"#/components/headers/X-RateLimit-Reset"},"X-RateLimit-Policy":{"$ref":"#/components/headers/X-RateLimit-Policy"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Deleted"}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"422":{"$ref":"#/components/responses/UnprocessableEntity"},"429":{"$ref":"#/components/responses/TooManyRequests"},"500":{"$ref":"#/components/responses/InternalServerError"},"503":{"$ref":"#/components/responses/ServiceUnavailable"}}}},"/v1/template":{"post":{"tags":["Templates"],"operationId":"createTemplate","summary":"Store a template","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["html"],"properties":{"html":{"type":"string","description":"The template as plain HTML, with {{variables}}."},"variables":{"$ref":"#/components/schemas/Variables","description":"Default values for the template's variables."},"name":{"type":"string","minLength":1,"maxLength":120},"description":{"type":"string","maxLength":300},"viewportWidth":{"type":"integer","description":"The template's default width in pixels."},"viewportHeight":{"type":"integer","description":"The template's default height in pixels."}}}}}},"responses":{"200":{"description":"The template was stored. Servers before 2026-10-27 answered 200; handle both.","headers":{"X-Request-ID":{"$ref":"#/components/headers/X-Request-ID"},"X-RateLimit-Limit":{"$ref":"#/components/headers/X-RateLimit-Limit"},"X-RateLimit-Remaining":{"$ref":"#/components/headers/X-RateLimit-Remaining"},"X-RateLimit-Reset":{"$ref":"#/components/headers/X-RateLimit-Reset"},"X-RateLimit-Policy":{"$ref":"#/components/headers/X-RateLimit-Policy"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/TemplateWrite"}}}},"201":{"description":"The template was stored.","headers":{"Location":{"description":"The new template, as `/v1/template/<id>`.","schema":{"type":"string"}},"X-Request-ID":{"$ref":"#/components/headers/X-Request-ID"},"X-RateLimit-Limit":{"$ref":"#/components/headers/X-RateLimit-Limit"},"X-RateLimit-Remaining":{"$ref":"#/components/headers/X-RateLimit-Remaining"},"X-RateLimit-Reset":{"$ref":"#/components/headers/X-RateLimit-Reset"},"X-RateLimit-Policy":{"$ref":"#/components/headers/X-RateLimit-Policy"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/TemplateWrite"}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"413":{"$ref":"#/components/responses/PayloadTooLarge"},"415":{"$ref":"#/components/responses/UnsupportedMediaType"},"422":{"$ref":"#/components/responses/UnprocessableEntity"},"429":{"$ref":"#/components/responses/TooManyRequests"},"500":{"$ref":"#/components/responses/InternalServerError"},"503":{"$ref":"#/components/responses/ServiceUnavailable"},"504":{"$ref":"#/components/responses/GatewayTimeout"}}},"get":{"tags":["Templates"],"operationId":"listTemplates","summary":"List your templates","parameters":[{"$ref":"#/components/parameters/page"},{"$ref":"#/components/parameters/pageSize"},{"$ref":"#/components/parameters/sort"}],"responses":{"200":{"description":"One page of templates.","headers":{"X-Request-ID":{"$ref":"#/components/headers/X-Request-ID"},"X-RateLimit-Limit":{"$ref":"#/components/headers/X-RateLimit-Limit"},"X-RateLimit-Remaining":{"$ref":"#/components/headers/X-RateLimit-Remaining"},"X-RateLimit-Reset":{"$ref":"#/components/headers/X-RateLimit-Reset"},"X-RateLimit-Policy":{"$ref":"#/components/headers/X-RateLimit-Policy"}},"content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/Page"},{"properties":{"items":{"type":"array","items":{"$ref":"#/components/schemas/TemplateListItem"}}}}]}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"422":{"$ref":"#/components/responses/UnprocessableEntity"},"429":{"$ref":"#/components/responses/TooManyRequests"},"500":{"$ref":"#/components/responses/InternalServerError"},"503":{"$ref":"#/components/responses/ServiceUnavailable"}}}},"/v1/template/preview":{"get":{"tags":["Templates"],"operationId":"getTemplatePreviews","summary":"Signed links to template preview images (used by the dashboard)","parameters":[{"name":"storagePaths","in":"query","required":true,"description":"Comma-separated preview storage paths.","schema":{"type":"string"}}],"responses":{"200":{"description":"A link per requested path, or an empty string for a path that is not one of your previews.","headers":{"X-Request-ID":{"$ref":"#/components/headers/X-Request-ID"},"X-RateLimit-Limit":{"$ref":"#/components/headers/X-RateLimit-Limit"},"X-RateLimit-Remaining":{"$ref":"#/components/headers/X-RateLimit-Remaining"},"X-RateLimit-Reset":{"$ref":"#/components/headers/X-RateLimit-Reset"},"X-RateLimit-Policy":{"$ref":"#/components/headers/X-RateLimit-Policy"}},"content":{"application/json":{"schema":{"type":"object","additionalProperties":{"type":"string"}}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"422":{"$ref":"#/components/responses/UnprocessableEntity"},"429":{"$ref":"#/components/responses/TooManyRequests"},"500":{"$ref":"#/components/responses/InternalServerError"},"503":{"$ref":"#/components/responses/ServiceUnavailable"}}}},"/v1/template/{templateId}":{"parameters":[{"name":"templateId","in":"path","required":true,"schema":{"type":"string"}}],"get":{"tags":["Templates"],"operationId":"getTemplate","summary":"Get one template","responses":{"200":{"description":"The template, with its HTML.","headers":{"X-Request-ID":{"$ref":"#/components/headers/X-Request-ID"},"X-RateLimit-Limit":{"$ref":"#/components/headers/X-RateLimit-Limit"},"X-RateLimit-Remaining":{"$ref":"#/components/headers/X-RateLimit-Remaining"},"X-RateLimit-Reset":{"$ref":"#/components/headers/X-RateLimit-Reset"},"X-RateLimit-Policy":{"$ref":"#/components/headers/X-RateLimit-Policy"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Template"}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"422":{"$ref":"#/components/responses/UnprocessableEntity"},"429":{"$ref":"#/components/responses/TooManyRequests"},"500":{"$ref":"#/components/responses/InternalServerError"},"503":{"$ref":"#/components/responses/ServiceUnavailable"}}},"put":{"tags":["Templates"],"operationId":"updateTemplate","summary":"Update a template","description":"Send any of the fields; the ones left out keep their value. A name or description alone changes it without rendering a new preview.","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","minProperties":1,"properties":{"html":{"type":"string","description":"The new HTML","plain.":null},"variables":{"$ref":"#/components/schemas/Variables","description":"New default values for the template's variables."},"name":{"type":"string","minLength":1,"maxLength":120},"description":{"type":"string","maxLength":300},"viewportWidth":{"type":"integer"},"viewportHeight":{"type":"integer"}}}}}},"responses":{"200":{"description":"The template was updated.","headers":{"X-Request-ID":{"$ref":"#/components/headers/X-Request-ID"},"X-RateLimit-Limit":{"$ref":"#/components/headers/X-RateLimit-Limit"},"X-RateLimit-Remaining":{"$ref":"#/components/headers/X-RateLimit-Remaining"},"X-RateLimit-Reset":{"$ref":"#/components/headers/X-RateLimit-Reset"},"X-RateLimit-Policy":{"$ref":"#/components/headers/X-RateLimit-Policy"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/TemplateWrite"}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"413":{"$ref":"#/components/responses/PayloadTooLarge"},"415":{"$ref":"#/components/responses/UnsupportedMediaType"},"422":{"$ref":"#/components/responses/UnprocessableEntity"},"429":{"$ref":"#/components/responses/TooManyRequests"},"500":{"$ref":"#/components/responses/InternalServerError"},"503":{"$ref":"#/components/responses/ServiceUnavailable"},"504":{"$ref":"#/components/responses/GatewayTimeout"}}},"patch":{"tags":["Templates"],"operationId":"patchTemplate","summary":"Update a template","description":"The same as PUT. Plain JSON, not a merge patch.","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","minProperties":1,"properties":{"html":{"type":"string","description":"The new HTML","plain.":null},"variables":{"$ref":"#/components/schemas/Variables","description":"New default values for the template's variables."},"name":{"type":"string","minLength":1,"maxLength":120},"description":{"type":"string","maxLength":300},"viewportWidth":{"type":"integer"},"viewportHeight":{"type":"integer"}}}}}},"responses":{"200":{"description":"The template was updated.","headers":{"X-Request-ID":{"$ref":"#/components/headers/X-Request-ID"},"X-RateLimit-Limit":{"$ref":"#/components/headers/X-RateLimit-Limit"},"X-RateLimit-Remaining":{"$ref":"#/components/headers/X-RateLimit-Remaining"},"X-RateLimit-Reset":{"$ref":"#/components/headers/X-RateLimit-Reset"},"X-RateLimit-Policy":{"$ref":"#/components/headers/X-RateLimit-Policy"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/TemplateWrite"}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"413":{"$ref":"#/components/responses/PayloadTooLarge"},"415":{"$ref":"#/components/responses/UnsupportedMediaType"},"422":{"$ref":"#/components/responses/UnprocessableEntity"},"429":{"$ref":"#/components/responses/TooManyRequests"},"500":{"$ref":"#/components/responses/InternalServerError"},"503":{"$ref":"#/components/responses/ServiceUnavailable"},"504":{"$ref":"#/components/responses/GatewayTimeout"}}},"delete":{"tags":["Templates"],"operationId":"deleteTemplate","summary":"Delete a template","responses":{"200":{"description":"The template was deleted.","headers":{"X-Request-ID":{"$ref":"#/components/headers/X-Request-ID"},"X-RateLimit-Limit":{"$ref":"#/components/headers/X-RateLimit-Limit"},"X-RateLimit-Remaining":{"$ref":"#/components/headers/X-RateLimit-Remaining"},"X-RateLimit-Reset":{"$ref":"#/components/headers/X-RateLimit-Reset"},"X-RateLimit-Policy":{"$ref":"#/components/headers/X-RateLimit-Policy"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Deleted"}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"422":{"$ref":"#/components/responses/UnprocessableEntity"},"429":{"$ref":"#/components/responses/TooManyRequests"},"500":{"$ref":"#/components/responses/InternalServerError"},"503":{"$ref":"#/components/responses/ServiceUnavailable"}}}},"/v1/credits":{"get":{"tags":["Credits"],"operationId":"getCredits","summary":"Your credit balance","responses":{"200":{"description":"Credits left and the total of the current batches.","headers":{"X-Request-ID":{"$ref":"#/components/headers/X-Request-ID"},"X-RateLimit-Limit":{"$ref":"#/components/headers/X-RateLimit-Limit"},"X-RateLimit-Remaining":{"$ref":"#/components/headers/X-RateLimit-Remaining"},"X-RateLimit-Reset":{"$ref":"#/components/headers/X-RateLimit-Reset"},"X-RateLimit-Policy":{"$ref":"#/components/headers/X-RateLimit-Policy"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Credits"}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/TooManyRequests"},"500":{"$ref":"#/components/responses/InternalServerError"},"503":{"$ref":"#/components/responses/ServiceUnavailable"}}}},"/v1/renders/{id}":{"get":{"tags":["Renders"],"operationId":"getRenderRequest","summary":"The status of an async render","description":"A render request sent with `Prefer: respond-async` answers 202 with this address in `Location`. Poll it, waiting `Retry-After` seconds while it is `pending`. Kept 24 hours, then 404. See https://ojo.so/docs/async-renders.","parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"The request's status; `Retry-After` is sent while it is `pending`.","headers":{"Retry-After":{"$ref":"#/components/headers/Retry-After"},"X-Request-ID":{"$ref":"#/components/headers/X-Request-ID"},"X-RateLimit-Limit":{"$ref":"#/components/headers/X-RateLimit-Limit"},"X-RateLimit-Remaining":{"$ref":"#/components/headers/X-RateLimit-Remaining"},"X-RateLimit-Reset":{"$ref":"#/components/headers/X-RateLimit-Reset"},"X-RateLimit-Policy":{"$ref":"#/components/headers/X-RateLimit-Policy"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/RenderRequest"}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"429":{"$ref":"#/components/responses/TooManyRequests"},"500":{"$ref":"#/components/responses/InternalServerError"},"503":{"$ref":"#/components/responses/ServiceUnavailable"}}}}},"components":{"securitySchemes":{"apiKey":{"type":"http","scheme":"bearer","description":"An API key from https://ojo.so/dashboard, sent as `Authorization: Bearer <key>`."}},"parameters":{"page":{"name":"page","in":"query","description":"The page, from 1. Missing or 0 means 1. At most 1000000.","schema":{"type":"integer","minimum":0,"maximum":1000000}},"pageSize":{"name":"pageSize","in":"query","description":"Items per page. Missing or 0 means 10; more than 100 is served as 100.","schema":{"type":"integer","minimum":0}},"sort":{"name":"sort","in":"query","description":"By creation time. Default `desc`, newest first.","schema":{"type":"string","enum":["asc","desc"]}},"IdempotencyKey":{"name":"Idempotency-Key","in":"header","required":false,"description":"A key of your choosing, 1 to 255 visible ASCII characters, that makes a retry safe: the same request sent again with the same key within 24 hours gets the first answer replayed and spends nothing. Keys are per account. See https://ojo.so/docs/idempotency.","schema":{"type":"string","minLength":1,"maxLength":255}},"Prefer":{"name":"Prefer","in":"header","required":false,"description":"`respond-async` answers 202 once the render is queued, with its status at `GET /v1/renders/{id}` (RFC 7240). Without it the request waits for the render. See https://ojo.so/docs/async-renders.","schema":{"type":"string","example":"respond-async"}}},"headers":{"X-Request-ID":{"description":"This request's id. Quote it when you report a problem.","schema":{"type":"string"}},"X-RateLimit-Limit":{"description":"Requests allowed in the current window.","schema":{"type":"integer"}},"X-RateLimit-Remaining":{"description":"Requests left in the current window.","schema":{"type":"integer"}},"X-RateLimit-Reset":{"description":"When the window resets, as an ISO 8601 time.","schema":{"type":"string","format":"date-time"}},"X-RateLimit-Policy":{"description":"The limit that applies, as `<name>:<requests>/<seconds>s`.","schema":{"type":"string"}},"Retry-After":{"description":"Seconds to wait before trying again.","schema":{"type":"integer","minimum":1}},"Idempotent-Replayed":{"description":"`true` when the answer is the stored one of an earlier request with the same Idempotency-Key.","schema":{"type":"string","enum":["true"]}},"Preference-Applied":{"description":"`respond-async` when the request was answered 202 and runs on.","schema":{"type":"string","enum":["respond-async"]}},"Location":{"description":"The address of the async render's status, `/v1/renders/{id}`.","schema":{"type":"string"}},"WWW-Authenticate":{"description":"The scheme the API wants, `Bearer realm=\"api.ojo.so\"`, with `error=\"invalid_token\"` for a refused key.","schema":{"type":"string"}}},"responses":{"BadRequest":{"description":"The body is not valid JSON, or a bulk request asks for more images than the rate limit allows.","headers":{"X-Request-ID":{"$ref":"#/components/headers/X-Request-ID"},"X-RateLimit-Limit":{"$ref":"#/components/headers/X-RateLimit-Limit"},"X-RateLimit-Remaining":{"$ref":"#/components/headers/X-RateLimit-Remaining"},"X-RateLimit-Reset":{"$ref":"#/components/headers/X-RateLimit-Reset"},"X-RateLimit-Policy":{"$ref":"#/components/headers/X-RateLimit-Policy"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}},"application/problem+json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"RenderBadRequest":{"description":"The body is not valid JSON, a bulk request asks for more images than the rate limit allows, or the Idempotency-Key is empty, longer than 255 characters or holds a character outside visible ASCII (`code` is `idempotency_key_invalid`).","headers":{"X-Request-ID":{"$ref":"#/components/headers/X-Request-ID"},"X-RateLimit-Limit":{"$ref":"#/components/headers/X-RateLimit-Limit"},"X-RateLimit-Remaining":{"$ref":"#/components/headers/X-RateLimit-Remaining"},"X-RateLimit-Reset":{"$ref":"#/components/headers/X-RateLimit-Reset"},"X-RateLimit-Policy":{"$ref":"#/components/headers/X-RateLimit-Policy"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}},"application/problem+json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"IdempotencyKeyInProgress":{"description":"A request with this Idempotency-Key is still running (`code` is `idempotency_key_in_progress`). Send the same request again after `retryAfter` seconds to get its answer.","headers":{"Retry-After":{"$ref":"#/components/headers/Retry-After"},"X-Request-ID":{"$ref":"#/components/headers/X-Request-ID"},"X-RateLimit-Limit":{"$ref":"#/components/headers/X-RateLimit-Limit"},"X-RateLimit-Remaining":{"$ref":"#/components/headers/X-RateLimit-Remaining"},"X-RateLimit-Reset":{"$ref":"#/components/headers/X-RateLimit-Reset"},"X-RateLimit-Policy":{"$ref":"#/components/headers/X-RateLimit-Policy"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}},"application/problem+json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"RenderUnprocessableEntity":{"description":"The request cannot be served as sent, or the render failed; `logs` says why. Or the Idempotency-Key was sent before with a different request (`code` is `idempotency_key_reused`): send a new key for a new request.","headers":{"X-Request-ID":{"$ref":"#/components/headers/X-Request-ID"},"X-RateLimit-Limit":{"$ref":"#/components/headers/X-RateLimit-Limit"},"X-RateLimit-Remaining":{"$ref":"#/components/headers/X-RateLimit-Remaining"},"X-RateLimit-Reset":{"$ref":"#/components/headers/X-RateLimit-Reset"},"X-RateLimit-Policy":{"$ref":"#/components/headers/X-RateLimit-Policy"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}},"application/problem+json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"Unauthorized":{"description":"No API key, or one that is not valid or was revoked.","headers":{"WWW-Authenticate":{"$ref":"#/components/headers/WWW-Authenticate"},"X-Request-ID":{"$ref":"#/components/headers/X-Request-ID"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}},"application/problem+json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"PaymentRequired":{"description":"Not enough credits for this render, so it is refused before it renders and nothing is spent. A PDF whose pages need more than its start amount can also get it once printed, when the rest does not fit: the PDF is deleted and the start amount refunded.","headers":{"X-Request-ID":{"$ref":"#/components/headers/X-Request-ID"},"X-RateLimit-Limit":{"$ref":"#/components/headers/X-RateLimit-Limit"},"X-RateLimit-Remaining":{"$ref":"#/components/headers/X-RateLimit-Remaining"},"X-RateLimit-Reset":{"$ref":"#/components/headers/X-RateLimit-Reset"},"X-RateLimit-Policy":{"$ref":"#/components/headers/X-RateLimit-Policy"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}},"application/problem+json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"Forbidden":{"description":"The account is suspended, or the key may not use this route.","headers":{"X-Request-ID":{"$ref":"#/components/headers/X-Request-ID"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}},"application/problem+json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"NotFound":{"description":"Not found, or not yours.","headers":{"X-Request-ID":{"$ref":"#/components/headers/X-Request-ID"},"X-RateLimit-Limit":{"$ref":"#/components/headers/X-RateLimit-Limit"},"X-RateLimit-Remaining":{"$ref":"#/components/headers/X-RateLimit-Remaining"},"X-RateLimit-Reset":{"$ref":"#/components/headers/X-RateLimit-Reset"},"X-RateLimit-Policy":{"$ref":"#/components/headers/X-RateLimit-Policy"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}},"application/problem+json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"PayloadTooLarge":{"description":"The request body is larger than 1 MB.","headers":{"X-Request-ID":{"$ref":"#/components/headers/X-Request-ID"},"X-RateLimit-Limit":{"$ref":"#/components/headers/X-RateLimit-Limit"},"X-RateLimit-Remaining":{"$ref":"#/components/headers/X-RateLimit-Remaining"},"X-RateLimit-Reset":{"$ref":"#/components/headers/X-RateLimit-Reset"},"X-RateLimit-Policy":{"$ref":"#/components/headers/X-RateLimit-Policy"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}},"application/problem+json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"UnsupportedMediaType":{"description":"The body is not of the type this route reads. `logs` names the one it wants.","headers":{"X-Request-ID":{"$ref":"#/components/headers/X-Request-ID"},"X-RateLimit-Limit":{"$ref":"#/components/headers/X-RateLimit-Limit"},"X-RateLimit-Remaining":{"$ref":"#/components/headers/X-RateLimit-Remaining"},"X-RateLimit-Reset":{"$ref":"#/components/headers/X-RateLimit-Reset"},"X-RateLimit-Policy":{"$ref":"#/components/headers/X-RateLimit-Policy"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}},"application/problem+json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"UnprocessableEntity":{"description":"The request cannot be served as sent, or the render failed. `logs` says why.","headers":{"X-Request-ID":{"$ref":"#/components/headers/X-Request-ID"},"X-RateLimit-Limit":{"$ref":"#/components/headers/X-RateLimit-Limit"},"X-RateLimit-Remaining":{"$ref":"#/components/headers/X-RateLimit-Remaining"},"X-RateLimit-Reset":{"$ref":"#/components/headers/X-RateLimit-Reset"},"X-RateLimit-Policy":{"$ref":"#/components/headers/X-RateLimit-Policy"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}},"application/problem+json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"TooManyRequests":{"description":"Over the rate limit, or (on POST /v1/pdf/template) another multi-page PDF from this account is still rendering. `code` names which; wait `retryAfter` seconds.","headers":{"Retry-After":{"$ref":"#/components/headers/Retry-After"},"X-Request-ID":{"$ref":"#/components/headers/X-Request-ID"},"X-RateLimit-Limit":{"$ref":"#/components/headers/X-RateLimit-Limit"},"X-RateLimit-Remaining":{"$ref":"#/components/headers/X-RateLimit-Remaining"},"X-RateLimit-Reset":{"$ref":"#/components/headers/X-RateLimit-Reset"},"X-RateLimit-Policy":{"$ref":"#/components/headers/X-RateLimit-Policy"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}},"application/problem+json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"InternalServerError":{"description":"Something failed on our side.","headers":{"X-Request-ID":{"$ref":"#/components/headers/X-Request-ID"},"X-RateLimit-Limit":{"$ref":"#/components/headers/X-RateLimit-Limit"},"X-RateLimit-Remaining":{"$ref":"#/components/headers/X-RateLimit-Remaining"},"X-RateLimit-Reset":{"$ref":"#/components/headers/X-RateLimit-Reset"},"X-RateLimit-Policy":{"$ref":"#/components/headers/X-RateLimit-Policy"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}},"application/problem+json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"ServiceUnavailable":{"description":"A service this request needs is unavailable. `code` is one of `auth_unavailable`, `database_unavailable`, `queue_unavailable`, `queue_full` or `render_unavailable`; try again after `retryAfter` seconds.","headers":{"Retry-After":{"$ref":"#/components/headers/Retry-After"},"X-Request-ID":{"$ref":"#/components/headers/X-Request-ID"},"X-RateLimit-Limit":{"$ref":"#/components/headers/X-RateLimit-Limit"},"X-RateLimit-Remaining":{"$ref":"#/components/headers/X-RateLimit-Remaining"},"X-RateLimit-Reset":{"$ref":"#/components/headers/X-RateLimit-Reset"},"X-RateLimit-Policy":{"$ref":"#/components/headers/X-RateLimit-Policy"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}},"application/problem+json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"GatewayTimeout":{"description":"The render did not finish in time (`code` is `render_timeout`). Nothing was charged.","headers":{"X-Request-ID":{"$ref":"#/components/headers/X-Request-ID"},"X-RateLimit-Limit":{"$ref":"#/components/headers/X-RateLimit-Limit"},"X-RateLimit-Remaining":{"$ref":"#/components/headers/X-RateLimit-Remaining"},"X-RateLimit-Reset":{"$ref":"#/components/headers/X-RateLimit-Reset"},"X-RateLimit-Policy":{"$ref":"#/components/headers/X-RateLimit-Policy"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}},"application/problem+json":{"schema":{"$ref":"#/components/schemas/Error"}}}}},"schemas":{"RenderRequest":{"type":"object","description":"An async render request. `result` is the answer the request gives without `Prefer: respond-async`; `error` is its error answer, with `statusCode` its HTTP status.","required":["id","status","createdAt"],"properties":{"id":{"type":"string","format":"uuid"},"status":{"type":"string","enum":["pending","succeeded","failed"]},"createdAt":{"type":"string","format":"date-time"},"completedAt":{"type":"string","format":"date-time"},"statusCode":{"type":"integer","description":"The HTTP status the request would have answered."},"result":{"description":"When `succeeded`, the body the request would have answered.","type":"object"},"error":{"description":"When `failed`, the error body the request would have answered. `code` is `render_lost` when the render was lost before it finished: send the request again.","$ref":"#/components/schemas/Error"}}},"Error":{"type":"object","description":"RFC 9457 problem details. `type` is left out, so it means `about:blank`; `code` names the cause where one is stable. The other members are extensions a problem-details client ignores.","required":["error","message","status","title","detail"],"properties":{"error":{"const":true},"message":{"type":"string"},"status":{"type":"integer","description":"The HTTP status."},"title":{"type":"string","description":"The status phrase, such as \"Not Found\"."},"detail":{"type":"string","description":"The same text as `message`."},"logs":{"type":"array","items":{"type":"string"},"description":"Details, such as each problem with the request."},"code":{"type":"string","description":"A stable name for the cause of a 400, 409, 422, 429, 503 or 504, such as `rate_limited.render`, `queue_full` or `idempotency_key_reused`."},"retryAfter":{"type":"integer","description":"Seconds to wait before trying again, as in the Retry-After header."}}},"Variables":{"type":"object","description":"Values for a template's variables, by name.","additionalProperties":true},"ImageRender":{"type":"object","required":["id","imageUrl","createdAt"],"properties":{"id":{"type":"string"},"imageUrl":{"type":"string","description":"The image link. It can come without a scheme; prefix `https://` when it does."},"createdAt":{"type":"string","format":"date-time"},"warnings":{"type":"array","items":{"type":"string"},"description":"Variables the template used but the request left out, resources that did not load, and body fields the route does not read. Absent when there were none."}}},"BulkRender":{"type":"object","required":["images","totalRequested","successCount","failureCount"],"properties":{"images":{"type":"array","items":{"type":"object","required":["id","templateId","imageUrl","createdAt"],"properties":{"id":{"type":"string"},"templateId":{"type":"string"},"imageUrl":{"type":"string","description":"The image link. It can come without a scheme; prefix `https://` when it does."},"createdAt":{"type":"string","format":"date-time"},"warnings":{"type":"array","items":{"type":"string"}}}}},"errors":{"type":"array","description":"One entry per image that failed. Absent when none did.","items":{"type":"object","required":["templateId","modify","error"],"properties":{"templateId":{"type":"string"},"modify":{"$ref":"#/components/schemas/Variables"},"error":{"type":"string"},"logs":{"type":"array","items":{"type":"string"}}}}},"totalRequested":{"type":"integer"},"successCount":{"type":"integer"},"failureCount":{"type":"integer"},"warnings":{"type":"array","items":{"type":"string"},"description":"Body fields the request sent that the route does not read. Absent when there were none."}}},"PdfRender":{"type":"object","required":["id","pdfUrl","pages","credits","createdAt"],"properties":{"id":{"type":"string"},"pdfUrl":{"type":"string","description":"The PDF link. It can come without a scheme; prefix `https://` when it does."},"pages":{"type":"integer","minimum":1},"credits":{"type":"integer","minimum":1,"description":"Credits spent","one per 20 pages.":null},"entries":{"type":"array","description":"Pages per entry of `pages`, in order. Only for a request that sent `pages`.","items":{"type":"object","required":["index","pages"],"properties":{"index":{"type":"integer"},"pages":{"type":"integer"}}}},"createdAt":{"type":"string","format":"date-time"},"warnings":{"type":"array","items":{"type":"string"},"description":"Variables the template used but the request left out, resources that did not load, and body fields the route does not read. Absent when there were none."}}},"ImageRecord":{"type":"object","required":["id","imageUrl","createdAt"],"description":"A stored image. A record from before ids and links were stored has null in their place.","properties":{"id":{"type":["string","null"]},"imageUrl":{"type":["string","null"]},"createdAt":{"type":"string"}}},"PdfRecord":{"type":"object","required":["id","pdfUrl","createdAt"],"description":"A stored PDF. A record from before ids and links were stored has null in their place.","properties":{"id":{"type":["string","null"]},"pdfUrl":{"type":["string","null"]},"pages":{"type":["integer","null"]},"createdAt":{"type":"string"}}},"Page":{"type":"object","required":["items","totalItems","totalPages","currentPage","pageSize"],"properties":{"items":{"type":"array"},"totalItems":{"type":"integer"},"totalPages":{"type":"integer"},"currentPage":{"type":"integer"},"pageSize":{"type":"integer"}}},"TemplateListItem":{"type":"object","description":"The snake_case fields repeat the camelCase ones and stay for the life of /v1.","required":["id","template_id"],"properties":{"id":{"type":"string"},"name":{"type":["string","null"]},"description":{"type":["string","null"]},"variables":{"description":"The template's default variable values","as stored.":null},"inputs":{"type":"array","items":{"type":"string"},"description":"The variables the template's HTML uses."},"previewUrl":{"type":["string","null"],"description":"A signed link to the preview image."},"createdAt":{"type":"string"},"updatedAt":{"type":["string","null"]},"template_id":{"type":"string","deprecated":true,"description":"Use `id`."},"preview_url":{"type":["string","null"],"deprecated":true,"description":"Use `previewUrl`."},"created_at":{"type":"string","deprecated":true,"description":"Use `createdAt`."},"updated_at":{"type":["string","null"],"deprecated":true,"description":"Use `updatedAt`."}}},"Template":{"type":"object","required":["id","html"],"properties":{"id":{"type":"string"},"name":{"type":["string","null"]},"description":{"type":["string","null"]},"html":{"type":"string"},"variables":{"description":"The template's default variable values","as stored.":null},"inputs":{"type":"array","items":{"type":"string"},"description":"The variables the template's HTML uses."},"createdAt":{"type":"string"},"updatedAt":{"type":["string","null"]}}},"TemplateWrite":{"type":"object","required":["id","message"],"properties":{"id":{"type":"string"},"message":{"type":"string"},"warnings":{"type":"array","items":{"type":"string"},"description":"Body fields the route does not read. Absent when there were none."}}},"Deleted":{"type":"object","required":["deleted"],"properties":{"deleted":{"const":true}}},"ImageBatchDelete":{"type":"object","properties":{"successful":{"type":"array","items":{"type":"string"}},"failed":{"type":"array","items":{"type":"object","required":["imageId","reason"],"properties":{"imageId":{"type":"string"},"reason":{"type":"string"}}}},"totalProcessed":{"type":"integer"},"successCount":{"type":"integer"},"failureCount":{"type":"integer"}}},"PdfBatchDelete":{"type":"object","properties":{"successful":{"type":"array","items":{"type":"string"}},"failed":{"type":"array","items":{"type":"object","required":["pdfId","reason"],"properties":{"pdfId":{"type":"string"},"reason":{"type":"string"}}}},"totalProcessed":{"type":"integer"},"successCount":{"type":"integer"},"failureCount":{"type":"integer"}}},"Credits":{"type":"object","required":["left","total","expiresAt"],"properties":{"left":{"type":"integer","description":"Credits you can spend now."},"total":{"type":"integer","description":"Credits in your current batches."},"expiresAt":{"type":["string","null"],"format":"date-time","description":"When the latest current batch expires, or null when you have none."}}}}}}