openapi: 3.2.0
info:
title: Renderwolf Templates API
version: 1.0.0
summary: Screenshots, PDFs, dynamic images and video through one API.
description: Renderwolf renders web pages to images, PDFs and video.
contact:
name: Ironfang
email: hello@ironfang.uk
url: https://ironfang.uk
termsOfService: https://ironfang.uk/legal/terms
license:
name: Proprietary - use governed by the Ironfang terms of service
url: https://ironfang.uk/legal/terms
servers:
- url: https://api.ironfang.uk/renderwolf
description: Production
- url: https://api.ironfang.uk
description: Production, unpartitioned - retained for existing clients
security:
- apiKey: []
tags:
- name: Templates
description: Stored HTML with `{{variable}}` placeholders, rendered on demand.
paths:
/v1/templates:
get:
tags:
- Templates
summary: List templates
operationId: listTemplates
responses:
'200':
description: 'Your templates, wrapped in a `templates` property - not a bare
array. Clients that iterate must read `templates`.
'
content:
application/json:
schema:
type: object
required:
- templates
properties:
templates:
type: array
items:
$ref: '#/components/schemas/Template'
example:
templates:
- id: 01a01c7c-682e-7b2a-95cd-ffb2f2a4d9be
name: og-card
width: 1200
height: 630
'401':
$ref: '#/components/responses/Unauthorized'
post:
tags:
- Templates
summary: Create a template
description: 'Store HTML containing `{{placeholders}}`. Render it with
`POST /v1/image/{id}`, supplying values for the placeholders.'
operationId: createTemplate
requestBody:
required: true
content:
application/json:
schema:
$ref: '#/components/schemas/TemplateInput'
examples:
og:
summary: An Open Graph card
value:
name: og-card
html:
{{title}}
width: 1200
height: 630
responses:
'201':
description: Created.
content:
application/json:
schema:
$ref: '#/components/schemas/Template'
'400':
$ref: '#/components/responses/BadRequest'
'401':
$ref: '#/components/responses/Unauthorized'
/v1/templates/{id}:
parameters:
- $ref: '#/components/parameters/TemplateId'
get:
tags:
- Templates
summary: Fetch a template
operationId: getTemplate
responses:
'200':
description: The template.
content:
application/json:
schema:
$ref: '#/components/schemas/Template'
'401':
$ref: '#/components/responses/Unauthorized'
'404':
$ref: '#/components/responses/NotFound'
put:
tags:
- Templates
summary: Replace a template
operationId: updateTemplate
requestBody:
required: true
content:
application/json:
schema:
$ref: '#/components/schemas/TemplateInput'
responses:
'200':
description: Updated.
content:
application/json:
schema:
$ref: '#/components/schemas/Template'
'400':
$ref: '#/components/responses/BadRequest'
'401':
$ref: '#/components/responses/Unauthorized'
'404':
$ref: '#/components/responses/NotFound'
delete:
tags:
- Templates
summary: Delete a template
operationId: deleteTemplate
responses:
'204':
description: Deleted.
'401':
$ref: '#/components/responses/Unauthorized'
'404':
$ref: '#/components/responses/NotFound'
/v1/image/{id}:
parameters:
- $ref: '#/components/parameters/TemplateId'
post:
tags:
- Templates
summary: Render a template
description: 'Substitutes `vars` into the template''s `{{placeholders}}` and renders it
at the template''s own width and height. Returns the image bytes.'
operationId: renderTemplate
requestBody:
required: true
content:
application/json:
schema:
$ref: '#/components/schemas/RenderTemplateRequest'
examples:
card:
value:
vars:
title: Shipping UUIDv7 everywhere
format: png
responses:
'200':
$ref: '#/components/responses/Image'
'400':
$ref: '#/components/responses/BadRequest'
'401':
$ref: '#/components/responses/Unauthorized'
'404':
$ref: '#/components/responses/NotFound'
'422':
$ref: '#/components/responses/RenderFailed'
'429':
$ref: '#/components/responses/RateLimited'
components:
schemas:
RenderCommon:
type: object
properties:
no_cache:
type: boolean
default: false
description: 'Force a fresh render instead of serving a cached one. Always
billable. Excluded from the cache key, so repeated fresh requests
do not collide.
'
block_ads:
type: boolean
default: false
description: 'Drop requests to known ad and tracker networks before they load.
Stopped as network requests rather than hidden afterwards, so the
page never spends time fetching them.
'
block_cookie_banners:
type: boolean
default: false
description: 'Hide common consent banners, and undo the scroll lock they set -
without which a full-page capture is one viewport tall.
Hidden, not accepted. Clicking "accept" would be a decision made
on the site owner''s behalf and recorded as theirs.
'
hide_selectors:
type: array
items:
type: string
description: 'CSS selectors to hide before capturing. Applied after load, so
elements injected by script are covered.
'
headers:
type: object
additionalProperties:
type: string
description: Extra HTTP headers sent with every request for the page.
cookies:
type: array
items:
$ref: '#/components/schemas/Cookie'
description: 'Cookies to set before navigating. Set beforehand because a session
cookie that arrives after load has missed the request it was meant
to authenticate.
'
authorization:
type: string
description: 'Sets the `Authorization` header - the common case, spelled once.
An explicit `Authorization` in `headers` wins over this.
'
user_agent:
type: string
description: 'Overrides the user agent, including any set by `device`.
'
wait_until:
type: string
enum:
- load
- domcontentloaded
- networkidle
default: load
description: 'When the page counts as ready. `networkidle` waits for traffic to
stop and is bounded by the render timeout, because a page with
polling never truly idles.
'
wait_for_selector:
type: string
description: 'Wait for this element before capturing. Fails the render if it
never appears, rather than returning a half-drawn page.
'
timeout_ms:
type: integer
description: 'Per-render timeout. Clamped to the service maximum - it can lower
the ceiling but not raise it.
'
device_scale_factor:
type: number
minimum: 1
maximum: 3
description: 'Pixel density. 2 is retina: the same CSS size at twice the pixels.
'
RenderTemplateRequest:
allOf:
- $ref: '#/components/schemas/RenderCommon'
- type: object
properties:
vars:
type: object
additionalProperties:
type: string
description: Values for the template's placeholders.
format:
type: string
enum:
- png
- jpeg
- webp
default: png
Cookie:
type: object
required:
- name
- domain
properties:
name:
type: string
value:
type: string
domain:
type: string
description: 'Required. Chromium silently drops a domainless cookie set before
navigation, so the render would quietly come back logged out.
'
path:
type: string
default: /
Error:
type: object
required:
- error
properties:
error:
type: object
required:
- code
- message
properties:
code:
type: string
description: Stable, safe to branch on.
enum:
- bad_request
- unauthorized
- invalid_api_key
- auth_failed
- forbidden
- email_unverified
- not_found
- quota_exhausted
- rate_limited
- target_rate_limited
- render_failed
- bad_signature
- signing_disabled
message:
type: string
description: Human-readable. May change; do not match on it.
example:
error:
code: target_rate_limited
message: too many renders for that host, try again shortly
Template:
type: object
properties:
id:
type: string
format: uuid
name:
type: string
html:
type: string
width:
type: integer
height:
type: integer
created_at:
type: string
format: date-time
updated_at:
type: string
format: date-time
TemplateInput:
type: object
required:
- name
- html
properties:
name:
type: string
html:
type: string
description: HTML with `{{placeholder}}` markers.
width:
type: integer
default: 1280
height:
type: integer
default: 800
responses:
RateLimited:
description: "Three distinct conditions share this status, separated by `error.code`:\n\n- `rate_limited` - your account exceeded 120 renders/minute\n- `target_rate_limited` - the site being rendered is being hit too hard\n across all customers (60/minute per host)\n- `quota_exhausted` - the plan's monthly allowance is spent\n\nThe first two clear within the minute, so retry with backoff. The third\ndoes not: upgrade in the portal or wait for the period to roll over.\nBranch on the code rather than the status.\n"
content:
application/json:
schema:
$ref: '#/components/schemas/Error'
NotFound:
description: No such template, or not yours.
content:
application/json:
schema:
$ref: '#/components/schemas/Error'
RenderFailed:
description: 'The page did not render: the host did not resolve, it never finished
loading, it timed out, or it was blocked. Try `delay_ms`, and check the
target loads in a normal browser.
'
content:
application/json:
schema:
$ref: '#/components/schemas/Error'
BadRequest:
description: Malformed body, or neither/both of `url` and `html`.
content:
application/json:
schema:
$ref: '#/components/schemas/Error'
Unauthorized:
description: Missing, malformed, revoked or unknown API key.
content:
application/json:
schema:
$ref: '#/components/schemas/Error'
Image:
description: 'The rendered image. `png` unless `format: jpeg` was requested.
'
headers:
X-Renderwolf-Cache:
description: '`hit` when served from cache, in which case it was free.'
schema:
type: string
enum:
- hit
X-Renderwolf-Credits:
description: 'What this call cost. `0` on a cache hit, because a cache hit is
free. Read it to log spend per request without polling
`/v1/usage`.
'
schema:
type: integer
X-Renderwolf-Render-Ms:
description: Render time in milliseconds, excluding any requested delay.
schema:
type: integer
X-Renderwolf-Delay-Ms:
description: The `delay_ms` actually waited, when one was requested.
schema:
type: integer
X-Renderwolf-Captured-At:
description: 'RFC 3339 timestamp of a fresh capture. Present only on
`no_cache` renders, where the response is also `no-store` - the
pairing is what makes a capture usable as evidence.
'
schema:
type: string
format: date-time
content:
image/png:
schema:
type: string
format: binary
image/jpeg:
schema:
type: string
format: binary
parameters:
TemplateId:
name: id
in: path
required: true
description: Template id (UUIDv7).
schema:
type: string
format: uuid
securitySchemes:
apiKey:
type: http
scheme: bearer
description: 'An API key from the portal, sent as `Authorization: Bearer rw_live_...`.
'