{
  "openapi": "3.1.0",
  "info": {
    "title": "rasterly",
    "version": "1.0.0",
    "description": "Turn any URL into a screenshot (PNG/JPEG), a PDF, clean Markdown or structured JSON over one HTTP call. A cheap, fast screenshot and web-render API for developers and AI agents. From $0.58 per 1,000 renders; a free tier of 100 renders/month, no card. Get a key at https://rasterly.dev. Also MCP-native (npx rasterly-mcp) and available pay-per-call over x402 on Base.",
    "contact": { "name": "rasterly", "url": "https://rasterly.dev", "email": "hello@rasterly.dev" },
    "license": { "name": "Proprietary", "url": "https://rasterly.dev" }
  },
  "servers": [{ "url": "https://api.rasterly.dev", "description": "Production" }],
  "security": [{ "ApiKeyAuth": [] }],
  "tags": [
    { "name": "Render", "description": "Turn a URL into an image, PDF, Markdown or JSON." },
    { "name": "Keys", "description": "Issue API keys. Remaining quota is returned on every render as the x-quota-remaining response header." },
    { "name": "Provenance", "description": "Verify signed provenance receipts (free, keyless)." }
  ],
  "paths": {
    "/v1/screenshot": {
      "get": {
        "tags": ["Render"],
        "summary": "Screenshot a URL",
        "description": "Render a URL to a PNG or JPEG image. Returns the raw image bytes.",
        "operationId": "screenshot",
        "parameters": [
          { "name": "url", "in": "query", "required": true, "description": "Public http(s) URL to capture.", "schema": { "type": "string", "format": "uri", "example": "https://stripe.com" } },
          { "name": "format", "in": "query", "required": false, "description": "Image format.", "schema": { "type": "string", "enum": ["png", "jpeg"], "default": "png" } },
          { "name": "full_page", "in": "query", "required": false, "description": "Capture the full scroll height instead of just the viewport.", "schema": { "type": "boolean", "default": false } },
          { "name": "width", "in": "query", "required": false, "description": "Viewport width in px.", "schema": { "type": "integer", "minimum": 16, "maximum": 4000, "default": 1280 } },
          { "name": "height", "in": "query", "required": false, "description": "Viewport height in px.", "schema": { "type": "integer", "minimum": 16, "maximum": 4000, "default": 800 } },
          { "name": "scale", "in": "query", "required": false, "description": "Device scale factor for retina crispness.", "schema": { "type": "integer", "minimum": 1, "maximum": 3, "default": 1 } },
          { "name": "quality", "in": "query", "required": false, "description": "JPEG quality (jpeg only).", "schema": { "type": "integer", "minimum": 1, "maximum": 100, "default": 80 } }
        ],
        "responses": {
          "200": { "description": "The rendered image.", "content": { "image/png": { "schema": { "type": "string", "format": "binary" } }, "image/jpeg": { "schema": { "type": "string", "format": "binary" } } } },
          "400": { "$ref": "#/components/responses/Error" },
          "401": { "$ref": "#/components/responses/Error" },
          "403": { "$ref": "#/components/responses/Error" }
        }
      }
    },
    "/v1/pdf": {
      "get": {
        "tags": ["Render"],
        "summary": "Render a URL to PDF",
        "description": "Render a URL to a PDF document. Returns application/pdf bytes.",
        "operationId": "pdf",
        "parameters": [
          { "name": "url", "in": "query", "required": true, "description": "Public http(s) URL to render.", "schema": { "type": "string", "format": "uri", "example": "https://stripe.com" } },
          { "name": "full_page", "in": "query", "required": false, "schema": { "type": "boolean", "default": true } }
        ],
        "responses": {
          "200": { "description": "The rendered PDF.", "content": { "application/pdf": { "schema": { "type": "string", "format": "binary" } } } },
          "400": { "$ref": "#/components/responses/Error" },
          "401": { "$ref": "#/components/responses/Error" }
        }
      }
    },
    "/v1/read": {
      "get": {
        "tags": ["Render"],
        "summary": "Read a URL as Markdown",
        "description": "Fetch a page and return clean, LLM-ready Markdown of its main content.",
        "operationId": "read",
        "parameters": [
          { "name": "url", "in": "query", "required": true, "schema": { "type": "string", "format": "uri", "example": "https://stripe.com" } }
        ],
        "responses": {
          "200": { "description": "The page as Markdown.", "content": { "text/markdown": { "schema": { "type": "string" } } } },
          "401": { "$ref": "#/components/responses/Error" }
        }
      }
    },
    "/v1/extract": {
      "get": {
        "tags": ["Render"],
        "summary": "Extract structured JSON from a URL",
        "description": "Pull structured data (title, description, OpenGraph, JSON-LD and fields you ask for) from a page as JSON.",
        "operationId": "extract",
        "parameters": [
          { "name": "url", "in": "query", "required": true, "schema": { "type": "string", "format": "uri", "example": "https://stripe.com" } }
        ],
        "responses": {
          "200": { "description": "Extracted JSON.", "content": { "application/json": { "schema": { "type": "object" } } } },
          "401": { "$ref": "#/components/responses/Error" }
        }
      }
    },
    "/v1/crawl": {
      "get": {
        "tags": ["Render"],
        "summary": "Crawl a site to Markdown",
        "description": "Follow same-origin links from a starting URL and return each page as clean, LLM-ready Markdown. Built for feeding a RAG index. Bounded by `limit`.",
        "operationId": "crawl",
        "parameters": [
          { "name": "url", "in": "query", "required": true, "description": "Public http(s) URL to start from.", "schema": { "type": "string", "format": "uri", "example": "https://stripe.com" } },
          { "name": "limit", "in": "query", "required": false, "description": "Maximum number of same-origin pages to crawl.", "schema": { "type": "integer", "minimum": 1, "maximum": 100, "default": 10 } }
        ],
        "responses": {
          "200": { "description": "The crawled pages.", "content": { "application/json": { "schema": { "type": "object", "properties": { "start": { "type": "string" }, "count": { "type": "integer" }, "pages": { "type": "array", "items": { "type": "object", "properties": { "url": { "type": "string" }, "title": { "type": "string" }, "markdown": { "type": "string" } } } } } } } } },
          "401": { "$ref": "#/components/responses/Error" }
        }
      }
    },
    "/v1/batch": {
      "post": {
        "tags": ["Render"],
        "summary": "Render many URLs in one call",
        "description": "Render a list of URLs in a single request, concurrently. Each result carries the rendered image as base64. Avoids N separate round-trips when thumbnailing a set of pages.",
        "operationId": "batch",
        "requestBody": {
          "required": true,
          "content": { "application/json": { "schema": {
            "type": "object",
            "required": ["items"],
            "properties": {
              "items": { "type": "array", "minItems": 1, "description": "One render per entry.", "items": { "type": "object", "required": ["url"], "properties": {
                "url": { "type": "string", "format": "uri", "example": "https://stripe.com" },
                "format": { "type": "string", "enum": ["png", "jpeg", "pdf"], "default": "png" },
                "full_page": { "type": "boolean", "default": false },
                "width": { "type": "integer" },
                "height": { "type": "integer" },
                "scale": { "type": "integer", "minimum": 1, "maximum": 3 }
              } } },
              "concurrency": { "type": "integer", "minimum": 1, "maximum": 8, "default": 4, "description": "How many to render in parallel." }
            }
          } } }
        },
        "responses": {
          "200": { "description": "Per-URL results.", "content": { "application/json": { "schema": { "type": "object", "properties": {
            "count": { "type": "integer" },
            "duration_ms": { "type": "integer" },
            "results": { "type": "array", "items": { "type": "object", "properties": {
              "index": { "type": "integer" },
              "ok": { "type": "boolean" },
              "bytes": { "type": "integer" },
              "content_type": { "type": "string" },
              "cache": { "type": "string" },
              "b64": { "type": "string", "description": "Base64-encoded rendered bytes." }
            } } }
          } } } } },
          "400": { "$ref": "#/components/responses/Error" },
          "401": { "$ref": "#/components/responses/Error" }
        }
      }
    },
    "/v1/keys": {
      "post": {
        "tags": ["Keys"],
        "summary": "Issue an API key",
        "description": "Get a free-tier API key on the spot for an email address. No card.",
        "operationId": "createKey",
        "security": [],
        "requestBody": {
          "required": true,
          "content": { "application/json": { "schema": { "type": "object", "required": ["email"], "properties": { "email": { "type": "string", "format": "email", "example": "you@company.com" } } } } }
        },
        "responses": {
          "200": { "description": "The issued key.", "content": { "application/json": { "schema": { "type": "object", "properties": { "key": { "type": "string", "example": "sk_live_..." }, "quota": { "type": "integer" }, "example": { "type": "string" } } } } } }
        }
      }
    },
    "/v1/verify": {
      "get": {
        "tags": ["Provenance"],
        "summary": "Verify a provenance receipt",
        "description": "Verify a signed provenance receipt from a read, extract or crawl response — cryptographic proof of what content came from which URL, and when. Free and keyless. Pass the receipt fields (returned as x-rasterly-receipt-* headers, or embedded in JSON with &receipt=1).",
        "operationId": "verify",
        "security": [],
        "parameters": [
          { "name": "url", "in": "query", "required": true, "description": "The source URL the receipt attests to.", "schema": { "type": "string", "format": "uri" } },
          { "name": "fetched_at", "in": "query", "required": true, "description": "The receipt timestamp (from x-rasterly-receipt-time).", "schema": { "type": "string" } },
          { "name": "hash", "in": "query", "required": true, "description": "The content SHA-256 (from x-rasterly-receipt-hash).", "schema": { "type": "string" } },
          { "name": "sig", "in": "query", "required": true, "description": "The receipt signature (from x-rasterly-receipt-sig).", "schema": { "type": "string" } }
        ],
        "responses": {
          "200": { "description": "Verification result.", "content": { "application/json": { "schema": { "type": "object", "properties": { "valid": { "type": "boolean" }, "url": { "type": "string" }, "fetched_at": { "type": "string" }, "content_sha256": { "type": "string" }, "note": { "type": "string" } } } } } }
        }
      }
    }
  },
  "components": {
    "securitySchemes": {
      "ApiKeyAuth": { "type": "apiKey", "in": "header", "name": "X-Api-Key", "description": "Your rasterly API key. Get one at https://rasterly.dev." }
    },
    "responses": {
      "Error": { "description": "Error.", "content": { "application/json": { "schema": { "type": "object", "properties": { "error": { "type": "string" } } } } } }
    }
  }
}
