Ironfang Signed URLs API

Shareable render URLs that need no API key.

Operations 2

POST /v1/sign Mint a signed render URL #
GET /v1/r/{exp}/{sig} Render from a signed URL #

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-signed-urls-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-signed-urls-api-openapi.yml Raw ↑
openapi: 3.2.0
info:
  title: Ironfang Render Signed URLs 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: Signed URLs
  description: Shareable render URLs that need no API key.
paths:
  /v1/sign:
    post:
      tags:
      - Signed URLs
      summary: Mint a signed render URL
      description: 'Returns a URL that renders on GET without an API key - safe to put in an

        `` tag or an email. The URL is bound to your account, so renders

        through it are admitted and counted against your organisation''s

        billing account like any other.


        A signed URL expires after 30 days unless `ttl_hours` says otherwise:

        any number of hours, or `0` for a URL that never expires, which is

        what an `og:image` tag needs. The response says when the URL

        expires.


        Requires signing to be configured on the deployment; returns `501`

        otherwise.'
      operationId: createSignedUrl
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/SignRequest'
            examples:
              screenshot:
                value:
                  kind: screenshot
                  url: https://example.com
                  width: 1280
                  ttl_hours: 24
              template:
                value:
                  kind: image
                  template: 0193f0c4-1f4e-7a91-9c3e-2b6f5d8a1e77
                  vars:
                    title: Hello
      responses:
        '200':
          description: The signed URL.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/SignedUrl'
        '400':
          $ref: '#/components/responses/BadRequest'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '404':
          $ref: '#/components/responses/NotFound'
        '501':
          description: Signing is not configured on this deployment.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
  /v1/r/{exp}/{sig}:
    get:
      tags:
      - Signed URLs
      summary: Render from a signed URL
      description: 'The endpoint a signed URL points at. Takes no API key - the signature is

        the credential - so it can be embedded directly in HTML or email. Call

        `POST /v1/sign` to mint one rather than constructing it by hand.'
      operationId: renderSignedUrl
      security: []
      parameters:
      - name: exp
        in: path
        required: true
        description: 'Expiry, in unix seconds, part of the signed payload. `0` never

          expires.

          '
        schema:
          type: string
      - name: sig
        in: path
        required: true
        description: The signature.
        schema:
          type: string
      responses:
        '200':
          $ref: '#/components/responses/Image'
        '403':
          description: Signature invalid or expired.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
        '404':
          description: Signing is not configured on this deployment.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
        '429':
          $ref: '#/components/responses/RateLimited'
        '402':
          $ref: '#/components/responses/BillingRefused'
        '503':
          $ref: '#/components/responses/BillingUnavailable'
components:
  responses:
    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'
    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
    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'
    NotFound:
      description: No such template, or not yours.
      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'
    Unauthorized:
      description: Missing, malformed, revoked or unknown API key.
      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:
    SignedUrl:
      type: object
      properties:
        url:
          type: string
          format: uri
          description: Absolute URL, ready to embed.
        path:
          type: string
          description: Path only, for serving behind your own domain.
        expires_at:
          type:
          - string
          - 'null'
          format: date-time
          description: When the URL stops rendering (UTC); null when it never expires.
    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
    SignRequest:
      type: object
      required:
      - kind
      properties:
        kind:
          type: string
          enum:
          - screenshot
          - image
          description: '`screenshot` signs a URL capture and requires `url`; `image` signs a

            template render and requires `template`.

            '
        url:
          type: string
          format: uri
          description: Required when `kind` is `screenshot`.
        template:
          type: string
          format: uuid
          description: Required when `kind` is `image`.
        vars:
          type: object
          additionalProperties:
            type: string
        width:
          type: integer
          maximum: 4096
        height:
          type: integer
          maximum: 4096
        full_page:
          type: boolean
          default: false
        ttl_hours:
          type: integer
          minimum: 0
          default: 720
          description: 'Hours until the URL expires. Leaving it out gives 720 (30 days);

            `0` never expires.

            '
  headers:
    Meter:
      description: 'The billing meter this request counted against, e.g.

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

        included.

        '
      schema:
        type: string
    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
  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