snaplab-dev Captures API

Stored renders, served from public `/i/c/{id}` URLs.

Operations 4

GET /api/captures List stored captures #
POST /api/captures Render and store a capture #
GET /api/captures/{id} Fetch one capture #
DELETE /api/captures/{id} Delete a capture #

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/snaplab-dev:snaplab-dev-captures-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

snaplab-dev-captures-api-openapi.yml Raw ↑
openapi: 3.2.0
info:
  title: snaplab Captures API
  version: 1.0.0
  summary: Turn any URL or HTML into a screenshot, a PDF, or a social image.
  description: 'snaplab renders web pages with a real headless Chromium and hands you the bytes.


    - `POST|GET /api/render` — URL **or** raw HTML → PNG / JPEG / WebP / AVIF / PDF, viewport or full page, retina, dark mode, element clipping.

    - `POST|GET /api/render/image` — HTML (or a `{{ template }}` + `data`) → a fixed-size image. The Open Graph card path.

    - `POST /api/render/preview` — unauthenticated, rate-limited demo render.

    - `POST /api/tools/*` — key-less public tools, 20 renders/day/IP, always answered with a hosted URL.

    - `/mcp` — a stateless Streamable-HTTP MCP server exposing `screenshot`, `pdf`, `html_to_image` and `usage`.


    ## Authentication


    Every endpoint except `/api/health`, `/api/render/preview` and `/api/tools/*` needs an API key. Keys look like `sk_…`

    and are created in the dashboard or through `POST /api/keys`.


    1. `Authorization: Bearer sk_…` — the header form, accepted everywhere.

    2. Signed query parameters — `GET /api/render` and `GET /api/render/image` only. See below.

    3. The browser session cookie, for the dashboard itself.


    ### Signed URLs


    Putting a raw key in an `` leaks it. Sign the URL instead: append `keyId` (the API key''s id) and

    `sig=hex(HMAC_SHA256(signing_secret, canonical))`, optionally with an `expires` Unix timestamp. The canonical string is

    every query pair except `sig`, sorted by name then value and joined with `&`. The per-key signing secret and a URL

    builder live in the dashboard under Keys.


    ## Options


    Options are camelCase and identical between the JSON body and the query string. In a query string, booleans are

    `true`/`1` and `false`/`0`, `hideSelectors` is a comma-separated list, and PDF options take either the dotted form

    (`pdf.format=A4`) or the flat alias (`pdfFormat=A4`).


    ## Caching and quota


    Identical options are served from a shared cache for 24h. **Cache hits and failed renders do not count against your

    quota** — only fresh, successful renders do. Renders carry `X-Snaplab-Cache`, `X-Snaplab-Duration-Ms`,

    `X-Snaplab-Fingerprint` and `X-Snaplab-Quota-Remaining`.


    Pass `cache: false` to force a fresh render.


    ## Errors


    Errors are JSON: `{"error":"…","code":"…"}`. Messages are for humans; codes are stable.


    | Status | Code | Meaning |

    | --- | --- | --- |

    | 400 | — | A parameter is missing, malformed, out of range, or the target is a private/blocked address. |

    | 401 | `unauthorized` | Missing, unknown or revoked API key; or a bad signature. |

    | 402 | `payment_required` | The subscription is past due. |

    | 422 | `selector_not_found` | The page rendered but `selector` matched nothing. |

    | 429 | `quota_exceeded` | The monthly render allowance is used up. |

    | 429 | — | Too many requests per minute for your plan. |

    | 502 | `upstream` | The target site was unreachable. |

    | 503 | `capacity` / `not_ready` | The render queue is saturated, or the browsers are still starting. Retry shortly. |

    | 504 | `timeout` | The page did not finish in time. |


    ## Plans


    | Plan | Renders / month | Price | Rate | Concurrency |

    | --- | --- | --- | --- | --- |

    | free | 100 | $0 | 30/min | 1 |

    | starter | 2,000 | $9 | 120/min | 3 |

    | pro | 10,000 | $29 | 300/min | 5 |

    | business | 40,000 | $79 | 600/min | 10 |'
  termsOfService: https://snaplab.dev/terms
  contact:
    name: snaplab support
    url: https://snaplab.dev/docs
    email: snaplab.dev
  license:
    name: MIT
    identifier: MIT
servers:
- url: https://snaplab.dev
  description: Production
security:
- bearerAuth: []
tags:
- name: Captures
  description: Stored renders, served from public `/i/c/{id}` URLs.
paths:
  /api/captures:
    get:
      tags:
      - Captures
      operationId: listCaptures
      summary: List stored captures
      security:
      - bearerAuth: []
      parameters:
      - name: limit
        in: query
        required: false
        schema:
          type: integer
          maximum: 100
          default: 30
        description: Page size.
      - name: cursor
        in: query
        required: false
        schema:
          type: string
        description: '`nextCursor` from the previous page (a Unix-seconds timestamp).'
      - name: status
        in: query
        required: false
        schema:
          type: string
          enum:
          - pending
          - processing
          - completed
          - failed
        description: Filter by status.
      - name: q
        in: query
        required: false
        schema:
          type: string
        description: Substring match on url or title.
      responses:
        '200':
          description: A page of captures, newest first.
          content:
            application/json:
              schema:
                type: object
                properties:
                  items:
                    type: array
                    items:
                      $ref: '#/components/schemas/Capture'
                  nextCursor:
                    type:
                    - string
                    - 'null'
        '401':
          description: Not authenticated.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
    post:
      tags:
      - Captures
      operationId: createCapture
      summary: Render and store a capture
      description: Renders synchronously and keeps the result, so it stays available at `/i/c/{id}` and in the dashboard. Pass `webhookUrl` to be called back when the capture finishes instead of polling — see `WebhookPayload`.
      security:
      - bearerAuth: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/CaptureRequest'
      responses:
        '200':
          description: The completed capture row.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Capture'
        '400':
          description: Invalid options, a target that resolves to a private/blocked address, or a `webhookUrl` that is not https or resolves to a private address.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
        '401':
          description: Missing or invalid API key / signature.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
        '402':
          description: Subscription past due (`payment_required`).
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
        '422':
          description: The render can never succeed as written (`selector_not_found`).
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
        '429':
          description: Rate limit or monthly quota exceeded (`quota_exceeded`).
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
        '502':
          description: The target site was unreachable (`upstream`).
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
        '503':
          description: Queue saturated or browsers not ready. Retry after `Retry-After` seconds.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
        '504':
          description: The page did not finish in time (`timeout`).
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
  /api/captures/{id}:
    parameters:
    - name: id
      in: path
      required: true
      schema:
        type: string
    get:
      tags:
      - Captures
      operationId: getCapture
      summary: Fetch one capture
      security:
      - bearerAuth: []
      responses:
        '200':
          description: The capture.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Capture'
        '401':
          description: Not authenticated.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
        '404':
          description: No such capture for this account.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
    delete:
      tags:
      - Captures
      operationId: deleteCapture
      summary: Delete a capture
      description: Removes the row and, when nothing else references them, the stored bytes.
      security:
      - bearerAuth: []
      responses:
        '200':
          description: Deleted.
          content:
            application/json:
              schema:
                type: object
                properties:
                  ok:
                    type: boolean
        '401':
          description: Not authenticated.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
        '404':
          description: No such capture for this account.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
components:
  schemas:
    WebhookDelivery:
      type: object
      description: One delivery attempt of a capture callback. Attempts run at 0s, 30s, 5min and 30min; after the fourth the row is `gave_up`.
      properties:
        id:
          type: string
        attempt:
          type: integer
          description: Attempts made so far, 1-4.
        status:
          type: string
          enum:
          - pending
          - ok
          - failed
          - gave_up
        lastStatusCode:
          type:
          - integer
          - 'null'
          description: HTTP status of the last attempt; null if the request never completed.
        updatedAt:
          type: string
          format: date-time
    Error:
      type: object
      required:
      - error
      properties:
        error:
          type: string
          description: Human-readable message.
        code:
          type: string
          description: Stable machine code, when one applies.
        retryable:
          type: boolean
        issues:
          type: array
          items:
            type: object
            additionalProperties: true
          description: Zod validation issues, for 400s.
        upgradeUrl:
          type: string
    CaptureWebhook:
      type: object
      description: Present on `GET /api/captures/{id}` only when a callback was set.
      properties:
        url:
          type: string
          format: uri
        deliveries:
          type: array
          items:
            $ref: '#/components/schemas/WebhookDelivery'
    CaptureRequest:
      type: object
      description: Every `RenderOptions` field, plus the optional callback. Omitted webhook fields fall back to the account defaults set with `PATCH /api/me`.
      allOf:
      - $ref: '#/components/schemas/RenderOptions'
      properties:
        webhookUrl:
          type: string
          format: uri
          maxLength: 2048
          description: '`https://` URL POSTed once the capture reaches `completed` or `failed`. Private, loopback, link-local and metadata addresses are refused with a 400.'
          example: https://api.example.com/hooks/snaplab
        webhookSecret:
          type: string
          maxLength: 256
          description: 'Signs the callback: `x-snaplab-signature: sha256=hex(HMAC_SHA256(secret, timestamp + "." + rawBody))`. Never returned by the API.'
    RenderOptions:
      type: object
      description: Exactly one of `url` or `html`/`template` must be given.
      properties:
        url:
          type: string
          maxLength: 2048
          format: uri
          description: Absolute http(s) URL to capture. Private, loopback, link-local and metadata addresses are refused.
        html:
          type: string
          maxLength: 2000000
          description: Raw HTML rendered instead of navigating. Subresources are still SSRF-filtered.
        template:
          type: string
          maxLength: 2000000
          description: HTML with `{{ path.to.value }}` placeholders, substituted from `data` and HTML-escaped.
        data:
          type: object
          additionalProperties: true
          description: Values for `template` (or for `html`, when no template is given).
        css:
          type: string
          maxLength: 500000
          description: Stylesheet injected into `<head>` before load. html/template mode only.
        googleFonts:
          type: string
          maxLength: 300
          description: Comma-separated Google Fonts families, e.g. `Inter,Playfair Display` (max 6).
        format:
          type: string
          enum:
          - png
          - jpeg
          - webp
          - avif
          - pdf
          default: png
        viewport:
          type: string
          enum:
          - desktop
          - laptop
          - tablet
          - mobile
          - wide
          default: desktop
          description: Preset size. `width`/`height`/`deviceScaleFactor` override individual axes.
        width:
          type: integer
          minimum: 120
          maximum: 3840
        height:
          type: integer
          minimum: 120
          maximum: 4320
        deviceScaleFactor:
          type: number
          minimum: 0.5
          maximum: 3
          description: Pixel ratio. 2 = retina.
        mobile:
          type: boolean
          description: 'Sugar for `viewport: "mobile"` (390x844 @3x, touch emulation).'
        fullPage:
          type: boolean
          default: true
          description: Capture the whole scrollable document. Ignored when `selector` is set.
        selector:
          type: string
          maxLength: 512
          description: Capture just this element. PDFs stay vector/selectable.
        quality:
          type: integer
          minimum: 1
          maximum: 100
          description: jpeg/webp/avif only.
        waitUntil:
          type: string
          enum:
          - auto
          - load
          - domcontentloaded
          - networkidle
          default: auto
          description: '"auto" waits for load, then for the network to be quiet for 150ms, capped at 400ms. "networkidle" is the full Playwright wait and costs ~0.5s more.'
        waitForSelector:
          type: string
          maxLength: 512
          description: Wait (up to 10s) for this selector before capturing.
        delayMs:
          type: integer
          minimum: 0
          maximum: 30000
        scrollY:
          type: integer
          minimum: 0
          maximum: 50000
          description: Scroll down this many pixels first (triggers lazy loading).
        hideSelectors:
          type: array
          maxItems: 20
          items:
            type: string
            maxLength: 512
          description: Elements to hide before capturing. Comma-separated in a query string.
        darkMode:
          oneOf:
          - type: boolean
          - type: string
            enum:
            - prefers
            - force
          description: '`true` (= `"force"`) emulates `prefers-color-scheme: dark` AND turns on Chromium''s auto dark mode, so a page with no dark theme comes back dark; a page that has one is left alone. `"prefers"` is the media query only.'
        dismissBanners:
          type: boolean
          default: true
          description: Hide known cookie/consent/chat banners with a curated stylesheet.
        blockAds:
          type: boolean
          default: false
          description: Abort requests to known ad, analytics and session-replay hosts.
        customCss:
          type: string
          maxLength: 20000
          description: CSS injected after load.
        customJs:
          type: string
          maxLength: 20000
          description: JavaScript evaluated after load.
        cookies:
          type: array
          maxItems: 40
          items:
            type: object
            required:
            - name
            - value
            properties:
              name:
                type: string
                maxLength: 256
              value:
                type: string
                maxLength: 4096
              domain:
                type: string
                maxLength: 256
                description: Defaults to the target host.
              path:
                type: string
                maxLength: 256
                default: /
              secure:
                type: boolean
              httpOnly:
                type: boolean
              sameSite:
                type: string
                enum:
                - Strict
                - Lax
                - None
              expires:
                type: integer
                description: Unix seconds. Omit for a session cookie.
            additionalProperties: false
          description: Per-request cookies. Renders with cookies are never cached or shared.
        headers:
          type: object
          additionalProperties:
            type: string
            maxLength: 8192
          description: Extra request headers.
        basicAuth:
          type: object
          required:
          - username
          - password
          properties:
            username:
              type: string
            password:
              type: string
          additionalProperties: false
        pdf:
          type: object
          description: PDF-only geometry. Ignored unless `format` is `pdf`.
          properties:
            format:
              type: string
              enum:
              - Letter
              - Legal
              - Tabloid
              - Ledger
              - A0
              - A1
              - A2
              - A3
              - A4
              - A5
              - A6
              default: A4
            landscape:
              type: boolean
              default: false
            printBackground:
              type: boolean
              default: true
            scale:
              type: number
              minimum: 0.1
              maximum: 2
              default: 1
            preferCSSPageSize:
              type: boolean
              default: false
              description: Honour the page's own `@page size` rule instead of `format`.
            margin:
              type: object
              description: CSS lengths (`0.4in`, `1cm`, `12mm`…).
              properties:
                top:
                  type: string
                  default: 0.4in
                right:
                  type: string
                  default: 0.4in
                bottom:
                  type: string
                  default: 0.4in
                left:
                  type: string
                  default: 0.4in
              additionalProperties: false
            headerTemplate:
              type: string
              maxLength: 20000
              description: HTML drawn at the top of every page, inside the top margin. It inherits none of the document's CSS, so set `font-size` explicitly and add your own side padding. Any element carrying the classes `pageNumber`, `totalPages`, `date`, `title` or `url` is filled in by Chromium.
            footerTemplate:
              type: string
              maxLength: 20000
              description: 'Same as `headerTemplate`, at the bottom of every page. Also accepts a built-in preset name: `@pageNumbers` (centred “Page N of M”) or `@pageNumbersUrl` (page numbers left, document URL right).'
            displayHeaderFooter:
              type: boolean
              description: Draw the running header and footer. Defaults to `true` as soon as either template is set; pass `false` to suppress both.
            pageRanges:
              type: string
              maxLength: 200
              description: 'Sheets to keep, in print-dialog syntax — `1-3`, `1,4,7-9`. Default: every page.'
              example: 1-3
            media:
              type: string
              enum:
              - screen
              - print
              default: screen
              description: Which stylesheet the paper is printed under. `print` honours the document's own print CSS (`@media print` rules, page breaks, hidden nav); `screen` keeps the on-screen look. Screenshots are always screen media.
            tagged:
              type: boolean
              default: false
              description: Emit a tagged (accessible) PDF with structure tags for screen readers.
            outline:
              type: boolean
              default: false
              description: Embed a document outline (PDF bookmarks) built from the page's headings.
            width:
              type: string
              maxLength: 40
              description: Exact paper width as a CSS length. Replaces `format`; ignored unless `height` is set too.
              example: 210mm
            height:
              type: string
              maxLength: 40
              description: Exact paper height as a CSS length.
              example: 297mm
            omitBackground:
              type: boolean
              default: false
              description: 'Drop background colours and images. The same knob as `printBackground: false`, and it wins over it.'
          additionalProperties: false
        filename:
          type: string
          maxLength: 120
          description: 'Suggested download name, sanitised. Sets `content-disposition: inline; filename="…"` on the returned bytes and on the hosted `/i/c/{id}` file (`?download=1` there makes it an `attachment`). Include the extension you want — it is used verbatim.'
          example: INV-2026-014.pdf
        cache:
          type: boolean
          default: true
          description: Set false to force a fresh render.
        responseType:
          type: string
          enum:
          - binary
          - json
          default: binary
          description: '`binary` streams the bytes; `json` stores the render and answers with a hosted URL.'
      additionalProperties: false
    Capture:
      type: object
      properties:
        id:
          type: string
        userId:
          type: string
        apiKeyId:
          type:
          - string
          - 'null'
        url:
          type: string
        title:
          type:
          - string
          - 'null'
        status:
          type: string
          enum:
          - pending
          - processing
          - completed
          - failed
        format:
          type: string
        selector:
          type:
          - string
          - 'null'
        fullPage:
          type: boolean
        width:
          type: integer
        height:
          type: integer
        deviceScaleFactor:
          type: integer
        darkMode:
          type: boolean
        fileKey:
          type:
          - string
          - 'null'
        fileSize:
          type:
          - integer
          - 'null'
        fingerprint:
          type:
          - string
          - 'null'
        error:
          type:
          - string
          - 'null'
        durationMs:
          type:
          - integer
          - 'null'
        cacheHit:
          type: boolean
        createdAt:
          type: string
          format: date-time
        completedAt:
          type:
          - string
          - 'null'
          format: date-time
        webhook:
          $ref: '#/components/schemas/CaptureWebhook'
          description: Only on `GET /api/captures/{id}`, and only when a callback was set.
  securitySchemes:
    bearerAuth:
      type: http
      scheme: bearer
      bearerFormat: sk_<40 chars>
      description: 'Send the key as `Authorization: Bearer sk_…`. This is also how the MCP server at `/mcp` authenticates.'
    signedKeyId:
      type: apiKey
      in: query
      name: keyId
      description: 'Signed-URL auth (GET renders only): the API key''s **id**, not its secret. Pair with `sig`.'
    signedSignature:
      type: apiKey
      in: query
      name: sig
      description: '`hex(HMAC_SHA256(signingSecret, canonical))`, where canonical is every query pair except `sig`, sorted by name then value and joined with `&`. Add `expires` (Unix seconds) to time-limit the URL.'
externalDocs:
  description: snaplab documentation
  url: https://snaplab.dev/docs