Ironfang Templates API

Stored HTML with `{{variable}}` placeholders, rendered on demand.

Operations 6

GET /v1/templates List templates #
POST /v1/templates Create a template #
GET /v1/templates/{id} Fetch a template #
PUT /v1/templates/{id} Replace a template #
DELETE /v1/templates/{id} Delete a template #
POST /v1/image/{id} Render a template #

Work with this as data

Every API here is available over the APIs.io API and to AI agents over MCP.

MCP server

One button, every client — Claude, Cursor, VS Code and the rest.

https://apis.io/mcp

Tools for apis

7 MCP tools reach this
  • find_apisBrowse and filter every API in the catalog.
  • get_api_artifactsOne API's artifacts, grouped by type.
  • get_openapiThe primary OpenAPI for this API.
  • find_similar_apisAPIs that look like this one.
  • apis_io_searchSTART HERE — APIs, providers and tags for one query, each with its total.
  • resolveTurn a domain, URL or GitHub org into the provider it belongs to.
  • find_cohortsEvery scored population of providers in the catalog.
All 92 tools →

Call it yourself

curl for this page
This API
curl "https://apis.io/api/v1/apis/ironfang:ironfang-templates-api"
All apis
curl "https://apis.io/api/v1/apis?limit=25"

Discovery needs no key. Ratings and market analysis are Pro.

Get an API key

Free tier, no form to fill in. Signing in shares your email address with us — we store it to create your key and to recognise you if you sign in with another provider. See our Privacy Policy and Terms.

A second provider on the same verified email joins the account you already have.

OpenAPI Specification

ironfang-templates-api-openapi.yml Raw ↑
openapi: 3.2.0
info:
  title: Ironfang Render Templates API
  version: 1.0.0
  summary: Screenshots, PDFs, dynamic images and video through one API.
  description: Ironfang Render renders web pages to images, PDFs and video.
  contact:
    name: Ironfang
    email: hello@ironfang.uk
    url: https://ironfang.com
  termsOfService: https://ironfang.com/legal/terms
  license:
    name: Proprietary - use governed by the Ironfang terms of service
    url: https://ironfang.com/legal/terms
servers:
- url: https://api.ironfang.com/render
  description: Production
- url: https://api.ironfang.com
  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
      description: Paged newest first. Follow next_cursor until empty; `total` is the whole count and does not change between pages. summary=true returns an empty html field; fetch a template by ID to edit or render it.
      parameters:
      - name: cursor
        in: query
        schema:
          type: string
          maxLength: 256
        description: Opaque next_cursor from the previous page; empty means the first page.
      - name: limit
        in: query
        schema:
          type: integer
          minimum: 1
          maximum: 100
          default: 50
        description: Maximum templates per page.
      - name: summary
        in: query
        schema:
          type: boolean
          default: false
        description: Omit HTML bodies from the database read.
      responses:
        '200':
          description: 'Your templates, wrapped in a `templates` property - not a bare

            array. Clients that iterate must read `templates`.

            '
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/TemplateList'
              example:
                total: 1
                next_cursor: ''
                templates:
                - id: 01a01c7c-682e-7b2a-95cd-ffb2f2a4d9be
                  name: og-card
                  width: 1200
                  height: 630
        '400':
          description: Malformed cursor, repeated or unknown parameter, or limit outside 1-100.
        '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: <div style="font:700 64px sans-serif;padding:80px">{{title}}</div>
                  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
      description: Returns a stored rendering template belonging to the authenticated account, including its HTML and output dimensions.
      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
      description: Replaces the authenticated account's stored template with the supplied name, HTML and dimensions. Subsequent renders use the updated template.
      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
      description: Removes a stored template from the authenticated account so it can no longer be used for new template renders.
      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'
        '403':
          $ref: '#/components/responses/Forbidden'
        '404':
          $ref: '#/components/responses/NotFound'
        '409':
          $ref: '#/components/responses/IdempotencyConflict'
        '422':
          $ref: '#/components/responses/RenderFailed'
        '429':
          $ref: '#/components/responses/RateLimited'
        '402':
          $ref: '#/components/responses/BillingRefused'
        '503':
          $ref: '#/components/responses/BillingUnavailable'
components:
  responses:
    NotFound:
      description: No such template, or not yours.
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/Error'
    Image:
      description: 'The rendered image. `png` unless `format: jpeg` was requested.

        '
      headers:
        X-Ironfang-Cache:
          description: '`hit` when served from cache, in which case it counted nothing.'
          schema:
            type: string
            enum:
            - hit
        X-Ironfang-Meter:
          $ref: '#/components/headers/Meter'
        X-Ironfang-Quantity:
          $ref: '#/components/headers/Quantity'
        X-Ironfang-Render-Ms:
          description: Render time in milliseconds, excluding any requested delay.
          schema:
            type: integer
        X-Ironfang-Delay-Ms:
          description: The `delay_ms` actually waited, when one was requested.
          schema:
            type: integer
        X-Ironfang-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
    Unauthorized:
      description: Missing, malformed, revoked or unknown API key.
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/Error'
    IdempotencyConflict:
      description: '`idempotency_key_used` - this Idempotency-Key already made its render

        (or is making it, or failed); nothing was reserved again.

        `idempotency_conflict` - the key was used for a different request.

        '
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/Error'
    BillingRefused:
      description: 'Paid usage cannot be billed: `paid_usage_paused`, `payment_required`,

        `payment_action_required` or `account_restricted`. Resolve it in

        Billing; nothing was reserved or counted. The same body can also

        carry `account_closed` (410) and `operation_too_large` (413).

        '
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/Error'
    BillingUnavailable:
      description: '`billing_temporarily_unavailable` - the billing ledger could not admit

        the request just now. Retry after `Retry-After` seconds; nothing was

        reserved.

        '
      headers:
        Retry-After:
          schema:
            type: integer
      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'
    Forbidden:
      description: '`insufficient_scope` - the key does not carry the scope;

        `account_not_linked` - the key''s account belongs to no Ironfang

        organisation, so its usage cannot be billed; `product_not_eligible` -

        paid use of Ironfang Render needs an eligible billing account.

        '
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/Error'
    RateLimited:
      description: "Rate limits and exhausted billing allowances share this status,\nseparated 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- `clip_busy`, `preview_busy` - every encoder or preview slot on the\n  worker is in use; `Retry-After` says when to come back\n- `concurrency_limit` - your account already has 2 browser\n  operations in progress, synchronous renders and running jobs\n  together; retry after `Retry-After`, or submit a durable job,\n  which waits for a free slot instead of being refused\n- `queue_full` - your account already has 1,000 unfinished jobs, or\n  the batch would take it past that; nothing was enqueued\n- `free_allowance_exhausted` - this month's free allowance for the\n  meter is used up and paid usage is not enabled\n- `account_budget_exhausted`, `product_budget_exhausted` - the\n  billing account's monthly limit, or Ironfang Render's, is reached\n- `exemption_limit_reached` - an exempt account's agreed limit is reached\n\nThe rate and capacity limits clear by themselves, so retry with\nbackoff, after `Retry-After` where it is sent. The billing codes do\nnot: enable paid usage or raise the limit in Billing, or wait for the\nmonth to roll over. Nothing was reserved or counted. Branch on the\ncode rather than the status.\n"
      headers:
        Retry-After:
          description: Seconds to wait before retrying, on the refusals that clear by themselves.
          schema:
            type: integer
      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'
  schemas:
    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. New codes may be added: treat a

                code you do not recognise as a failure of the class its HTTP

                status indicates.

                '
              enum:
              - bad_request
              - unauthorized
              - invalid_api_key
              - auth_failed
              - forbidden
              - billing_moved
              - not_found
              - rate_limited
              - target_rate_limited
              - render_failed
              - bad_signature
              - signing_disabled
              - account_gone
              - account_not_linked
              - account_required
              - admission_failed
              - bad_destination
              - bad_target
              - batch_plan_failed
              - batch_submit_failed
              - batch_too_large
              - bundle_failed
              - clip_busy
              - concurrency_limit
              - delegated_not_allowed
              - delivery_disabled
              - delivery_plan_failed
              - destination_disabled
              - idempotency_conflict
              - idempotency_key_used
              - insufficient_scope
              - invalid_token
              - job_finished
              - job_plan_failed
              - job_submit_failed
              - logo_fetch_failed
              - pdf_too_long
              - preview_busy
              - queue_full
              - result_expired
              - result_not_ready
              - result_read_failed
              - secret_failed
              - site_preview_disabled
              - store_failed
              - unknown_destination
              - unscannable
              - vault_failed
              - video_disabled
              - free_allowance_exhausted
              - account_budget_exhausted
              - product_budget_exhausted
              - paid_usage_paused
              - payment_required
              - payment_action_required
              - account_restricted
              - account_closed
              - product_not_eligible
              - exemption_limit_reached
              - operation_too_large
              - billing_temporarily_unavailable
            message:
              type: string
              description: Human-readable. May change; do not match on it.
            docs:
              type: string
              format: uri
              description: Where the error codes are documented.
        request_id:
          type: string
          description: The X-Ironfang-Request-ID of the failed request. Quote it to support.
      example:
        error:
          code: target_rate_limited
          message: too many renders for that host, try again shortly
          docs: https://ironfang.com/render/docs#errors
        request_id: 01992f6e-7c1a-7b3e-9d52-4f0a6c8e2b17
    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
    TemplateList:
      type: object
      additionalProperties: false
      required:
      - templates
      - next_cursor
      - total
      properties:
        templates:
          type: array
          maxItems: 100
          items:
            $ref: '#/components/schemas/Template'
        next_cursor:
          type: string
          description: Cursor for the next page; empty on the final page.
        total:
          type: integer
          minimum: 0
          description: How many templates the account has in total, independent of this page. Read it rather than counting pages.
    PageAction:
      description: 'One ordered pre-capture action. Selectors are CSS selectors in the main

        page. click and hover wait until the first matching element is

        interactable; wait_for_selector waits for its presence (not visibility).

        All actions share the remaining timeout_ms render budget.

        '
      oneOf:
      - type: object
        additionalProperties: false
        required:
        - type
        - selector
        properties:
          type:
            type: string
            enum:
            - click
            - hover
            - wait_for_selector
          selector:
            type: string
            minLength: 1
            maxLength: 1024
            description: Non-blank CSS selector; at most 1024 UTF-8 bytes.
      - type: object
        additionalProperties: false
        required:
        - type
        - duration_ms
        properties:
          type:
            type: string
            enum:
            - delay
          duration_ms:
            type: integer
            minimum: 0
            maximum: 10000
            description: Delay in milliseconds; all action delays together may total at most 10000 ms.
    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.

            '
        css:
          type: string
          maxLength: 65536
          example: '.chat-widget { display: none !important; }'
          description: 'Custom stylesheet, at most 65536 UTF-8 bytes. Applied after normal

            load/wait conditions and built-in hiding, before script, actions,

            settling and capture. Supports screenshots and PDFs, including

            @media print rules. Malformed stylesheet syntax is a 400

            bad_request; Chromium determines supported properties and values.

            '
        script:
          type: string
          maxLength: 16384
          example: document.querySelector('#pricing')?.click();
          description: 'JavaScript statements executed inside the target page''s main-frame

            context after load/wait conditions and css, before actions and

            capture. Runs as an async function body: use await or return a

            promise for asynchronous work. Completion is awaited; return values

            are discarded. At most 16384 UTF-8 bytes and 5000 ms execution,

            further bounded by the remaining timeout_ms budget. Syntax errors,

            execution failures and timeouts return 422 render_failed with a

            sanitized message. Supports screenshots and PDFs; no browser-session

            or Playwright/Puppeteer APIs are exposed.

            '
        actions:
          type: array
          maxItems: 20
          items:
            $ref: '#/components/schemas/PageAction'
          example:
          - type: click
            selector: '#show-more'
          - type: wait_for_selector
            selector: '#details'
          description: 'Up to 20 actions, executed in order after css and script, before

            settling and capture. Supports screenshots and PDFs. Missing or

            unready selectors and action timeouts fail with 422 render_failed,

            identifying the zero-based action index and type. Invalid action

            shapes are 400 bad_request. An action failure stops the list and is

            not retried in queued jobs. All options, including action order,

            are part of the screenshot cache key; use no_cache for a fresh run.

            '
        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.

            '
    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
  parameters:
    TemplateId:
      name: id
      in: path
      required: true
      description: Template id (UUIDv7).
      schema:
        type: string
        format: uuid
  headers:
    Quantity:
      description: 'The quantity this request committed to its meter: 1 for a unit

        render, output seconds (rounded up) for video, `0` for a cache hit or

        an unmetered key. Read it to log usage per request without polling

        `/v1/usage`.

        '
      schema:
        type: integer
    Meter:
      description: 'The billing meter this request counted against, e.g.

        `render.screenshot`. Present on every billable response, cache hits

        included.

        '
      schema:
        type: string
  securitySchemes:
    apiKey:
      type: http
      scheme: bearer
      description: 'An API key from the portal, sent as `Authorization: Bearer rw_live_...`.

        '
externalDocs:
  description: Ironfang Render documentation
  url: https://ironfang.com/render/docs