{
  "openapi": "3.1.0",
  "info": {
    "title": "SnapRender Screenshot API",
    "description": "Capture screenshots of any website as PNG, JPEG, WebP, or PDF. To take a screenshot, call GET /v1/screenshot with a url parameter and your API key in the X-API-Key header. Set response_type=json to receive a base64 data URI (recommended for AI agents). Features: device emulation, dark mode, ad blocking, and cookie banner removal. Free tier: 200 screenshots/month, no credit card required.",
    "version": "1.0.0",
    "contact": {
      "name": "SnapRender Support",
      "url": "https://snap-render.com",
      "email": "support@snap-render.com"
    }
  },
  "servers": [
    {
      "url": "https://app.snap-render.com",
      "description": "Production"
    }
  ],
  "paths": {
    "/v1/screenshot": {
      "post": {
        "operationId": "takeScreenshotPost",
        "summary": "Capture a screenshot from URL, HTML, or Markdown",
        "description": "Takes a screenshot and returns it as a binary image or JSON with base64 data URI. Accepts a JSON body with exactly one of: url (capture a website), html (render raw HTML), or markdown (render Markdown with a styled template). All screenshot parameters (format, width, height, dark_mode, etc.) work with all three sources. Use POST when providing html or markdown content. Supports device emulation, dark mode, ad blocking, cookie banner removal, and caching.",
        "x-openai-isConsequential": false,
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "url": { "type": "string", "description": "URL to capture (mutually exclusive with html and markdown)" },
                  "html": { "type": "string", "description": "Raw HTML content to render (max 2MB, mutually exclusive with url and markdown)" },
                  "markdown": { "type": "string", "description": "Markdown content to render with styled template (max 500KB, mutually exclusive with url and html)" },
                  "format": { "type": "string", "enum": ["png", "jpeg", "webp", "pdf"], "default": "png" },
                  "width": { "type": "integer", "minimum": 320, "maximum": 3840, "default": 1280 },
                  "height": { "type": "integer", "minimum": 200, "maximum": 10000, "default": 800 },
                  "full_page": { "type": "boolean", "default": false },
                  "quality": { "type": "integer", "minimum": 1, "maximum": 100, "default": 90 },
                  "delay": { "type": "integer", "minimum": 0, "maximum": 10000, "default": 0 },
                  "dark_mode": { "type": "boolean", "default": false },
                  "block_ads": { "type": "boolean", "default": true },
                  "block_cookie_banners": { "type": "boolean", "default": true },
                  "device": { "type": "string", "enum": ["iphone_14", "iphone_15_pro", "pixel_7", "ipad_pro", "macbook_pro"] },
                  "hide_selectors": { "type": "string" },
                  "click_selector": { "type": "string" },
                  "user_agent": { "type": "string" },
                  "cache": { "type": "boolean", "default": false, "description": "Return cached result if available. OFF by default: set true to enable caching. Cached hits are free but may be up to 24h old." },
                  "cache_ttl": { "type": "integer", "minimum": 0, "maximum": 2592000, "default": 86400, "description": "Cache lifetime in seconds (default 24h). Cached screenshots older than this are recaptured." },
                  "response_type": { "type": "string", "enum": ["binary", "json"], "default": "binary" }
                }
              },
              "examples": {
                "html": {
                  "summary": "Render HTML",
                  "value": { "html": "<html><body><h1>Hello World</h1></body></html>", "format": "png", "width": 800, "height": 600 }
                },
                "markdown": {
                  "summary": "Render Markdown",
                  "value": { "markdown": "# Hello World\n\nThis is **bold** text.", "format": "png" }
                },
                "url": {
                  "summary": "Capture URL (via POST)",
                  "value": { "url": "https://example.com", "format": "png" }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Screenshot captured successfully",
            "headers": {
              "X-Cache": { "schema": { "type": "string", "enum": ["HIT", "MISS"] } },
              "X-Response-Time": { "schema": { "type": "string" } },
              "X-Remaining-Credits": { "schema": { "type": "string" } },
              "X-Request-Id": { "schema": { "type": "string" } }
            },
            "content": {
              "image/png": { "schema": { "type": "string", "format": "binary" } },
              "image/jpeg": { "schema": { "type": "string", "format": "binary" } },
              "image/webp": { "schema": { "type": "string", "format": "binary" } },
              "application/pdf": { "schema": { "type": "string", "format": "binary" } },
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "url": { "type": ["string", "null"], "description": "The captured URL (null for html/markdown)" },
                    "source": { "type": "string", "enum": ["url", "html", "markdown"], "description": "Content source type" },
                    "format": { "type": "string" },
                    "width": { "type": "integer" },
                    "height": { "type": "integer" },
                    "image": { "type": "string", "description": "Base64 data URI" },
                    "size": { "type": "integer" },
                    "cache": { "type": "string", "enum": ["HIT", "MISS"] },
                    "responseTime": { "type": "string" },
                    "remainingCredits": { "type": "integer" }
                  }
                }
              }
            }
          },
          "400": { "description": "Invalid parameters", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } } },
          "401": { "description": "Invalid or missing API key", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } } },
          "429": { "description": "Rate limited or quota exceeded", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } } }
        },
        "security": [{ "apiKey": [] }]
      },
      "get": {
        "operationId": "takeScreenshot",
        "summary": "Capture a screenshot of a website",
        "description": "Takes a screenshot of any URL and returns it as a binary image or JSON with base64 data URI. Set response_type=json for AI agent integrations. Supports device emulation, dark mode, ad blocking, cookie banner removal, and caching.",
        "x-openai-isConsequential": false,
        "parameters": [
          {
            "name": "url",
            "in": "query",
            "required": true,
            "schema": { "type": "string", "format": "uri" },
            "description": "The URL to capture (must be http or https)",
            "example": "https://example.com"
          },
          {
            "name": "format",
            "in": "query",
            "schema": { "type": "string", "enum": ["png", "jpeg", "webp", "pdf"], "default": "png" },
            "description": "Output image format. Use png for lossless quality, jpeg/webp for smaller files, pdf for documents.",
            "example": "png"
          },
          {
            "name": "width",
            "in": "query",
            "schema": { "type": "integer", "minimum": 320, "maximum": 3840, "default": 1280 },
            "description": "Viewport width in pixels (320-3840)",
            "example": 1280
          },
          {
            "name": "height",
            "in": "query",
            "schema": { "type": "integer", "minimum": 200, "maximum": 10000, "default": 800 },
            "description": "Viewport height in pixels (200-10000)",
            "example": 800
          },
          {
            "name": "full_page",
            "in": "query",
            "schema": { "type": "string", "enum": ["true", "false"], "default": "false" },
            "description": "Capture the entire scrollable page instead of just the viewport",
            "example": "false"
          },
          {
            "name": "quality",
            "in": "query",
            "schema": { "type": "integer", "minimum": 1, "maximum": 100, "default": 90 },
            "description": "Image quality for JPEG and WebP (1-100). Ignored for PNG and PDF.",
            "example": 90
          },
          {
            "name": "delay",
            "in": "query",
            "schema": { "type": "integer", "minimum": 0, "maximum": 10000, "default": 0 },
            "description": "Milliseconds to wait after page load before capturing. Useful for pages with animations or lazy-loaded content.",
            "example": 0
          },
          {
            "name": "dark_mode",
            "in": "query",
            "schema": { "type": "string", "enum": ["true", "false"], "default": "false" },
            "description": "Emulate prefers-color-scheme: dark CSS media feature",
            "example": "false"
          },
          {
            "name": "block_ads",
            "in": "query",
            "schema": { "type": "string", "enum": ["true", "false"], "default": "true" },
            "description": "Block advertisements and trackers for cleaner screenshots",
            "example": "true"
          },
          {
            "name": "block_cookie_banners",
            "in": "query",
            "schema": { "type": "string", "enum": ["true", "false"], "default": "true" },
            "description": "Remove cookie consent banners and GDPR overlays",
            "example": "true"
          },
          {
            "name": "device",
            "in": "query",
            "schema": { "type": "string", "enum": ["iphone_14", "iphone_15_pro", "pixel_7", "ipad_pro", "macbook_pro"] },
            "description": "Device preset for mobile/tablet emulation. Sets viewport size, user agent, and device scale factor.",
            "example": "iphone_15_pro"
          },
          {
            "name": "hide_selectors",
            "in": "query",
            "schema": { "type": "string" },
            "description": "Comma-separated CSS selectors to hide before capturing (e.g., '.banner,.popup')",
            "example": ".cookie-notice,.newsletter-popup"
          },
          {
            "name": "click_selector",
            "in": "query",
            "schema": { "type": "string" },
            "description": "CSS selector to click before capturing (e.g., to dismiss a modal or expand content)",
            "example": "#accept-cookies"
          },
          {
            "name": "user_agent",
            "in": "query",
            "schema": { "type": "string" },
            "description": "Custom user agent string to use for the request"
          },
          {
            "name": "cache",
            "in": "query",
            "schema": { "type": "string", "enum": ["true", "false"], "default": "false" },
            "description": "Return cached result if available. OFF by default: set true to enable caching. Cached hits are free but may be up to 24h old.",
            "example": "false"
          },
          {
            "name": "cache_ttl",
            "in": "query",
            "schema": { "type": "integer", "minimum": 0, "maximum": 2592000, "default": 86400 },
            "description": "Cache lifetime in seconds (default 24h). Cached screenshots older than this are recaptured. Max depends on plan.",
            "example": 86400
          },
          {
            "name": "response_type",
            "in": "query",
            "schema": { "type": "string", "enum": ["binary", "json"], "default": "binary" },
            "description": "Response format. 'binary' returns raw image bytes with Content-Type header (default). 'json' returns a JSON object with a base64 data URI in the 'image' field — recommended for AI agents, ChatGPT Actions, and programmatic integrations.",
            "example": "json"
          }
        ],
        "responses": {
          "200": {
            "description": "Screenshot captured successfully",
            "headers": {
              "X-Cache": { "schema": { "type": "string", "enum": ["HIT", "MISS"] }, "description": "Whether the result was served from cache" },
              "X-Response-Time": { "schema": { "type": "string" }, "description": "Total response time (e.g., '1234ms')" },
              "X-Remaining-Credits": { "schema": { "type": "string" }, "description": "Remaining screenshots this billing period" },
              "X-Request-Id": { "schema": { "type": "string" }, "description": "Unique request identifier for support" }
            },
            "content": {
              "image/png": { "schema": { "type": "string", "format": "binary" } },
              "image/jpeg": { "schema": { "type": "string", "format": "binary" } },
              "image/webp": { "schema": { "type": "string", "format": "binary" } },
              "application/pdf": { "schema": { "type": "string", "format": "binary" } },
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "url": { "type": "string", "description": "The captured URL" },
                    "format": { "type": "string", "description": "Image format used" },
                    "width": { "type": "integer", "description": "Viewport width" },
                    "height": { "type": "integer", "description": "Viewport height" },
                    "image": { "type": "string", "description": "Base64 data URI of the screenshot (e.g., data:image/png;base64,...)" },
                    "size": { "type": "integer", "description": "Image size in bytes" },
                    "cache": { "type": "string", "enum": ["HIT", "MISS"], "description": "Cache status" },
                    "responseTime": { "type": "string", "description": "Render time (e.g., '1234ms')" },
                    "remainingCredits": { "type": "integer", "description": "Remaining screenshots this billing period" }
                  }
                },
                "example": {
                  "url": "https://example.com",
                  "format": "png",
                  "width": 1280,
                  "height": 800,
                  "image": "data:image/png;base64,iVBORw0KGgo...",
                  "size": 45320,
                  "cache": "MISS",
                  "responseTime": "1234ms",
                  "remainingCredits": 49
                }
              }
            }
          },
          "400": {
            "description": "Invalid parameters or blocked URL",
            "content": {
              "application/json": {
                "schema": { "$ref": "#/components/schemas/Error" }
              }
            }
          },
          "401": {
            "description": "Invalid or missing API key",
            "content": {
              "application/json": {
                "schema": { "$ref": "#/components/schemas/Error" }
              }
            }
          },
          "429": {
            "description": "Rate limited or quota exceeded",
            "content": {
              "application/json": {
                "schema": { "$ref": "#/components/schemas/Error" }
              }
            }
          }
        },
        "security": [{ "apiKey": [] }]
      }
    },
    "/v1/screenshot/info": {
      "get": {
        "operationId": "getScreenshotInfo",
        "summary": "Check screenshot cache status",
        "description": "Check if a screenshot is cached and get metadata without capturing. Does not count against quota. Useful for checking if a URL has been recently captured.",
        "x-openai-isConsequential": false,
        "parameters": [
          {
            "name": "url",
            "in": "query",
            "required": true,
            "schema": { "type": "string", "format": "uri" },
            "description": "The URL to check cache status for",
            "example": "https://example.com"
          },
          {
            "name": "format",
            "in": "query",
            "schema": { "type": "string", "enum": ["png", "jpeg", "webp", "pdf"], "default": "png" },
            "description": "Output format to check (cache keys are format-specific)",
            "example": "png"
          },
          {
            "name": "width",
            "in": "query",
            "schema": { "type": "integer", "default": 1280 },
            "description": "Viewport width in pixels (cache keys are resolution-specific)",
            "example": 1280
          },
          {
            "name": "height",
            "in": "query",
            "schema": { "type": "integer", "default": 800 },
            "description": "Viewport height in pixels (cache keys are resolution-specific)",
            "example": 800
          }
        ],
        "responses": {
          "200": {
            "description": "Cache information",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "url": { "type": "string" },
                    "cached": { "type": "boolean" },
                    "cachedAt": { "type": "string", "format": "date-time" },
                    "expiresAt": { "type": "string", "format": "date-time" },
                    "size": { "type": "integer" }
                  }
                }
              }
            }
          }
        },
        "security": [{ "apiKey": [] }]
      }
    },
    "/v1/screenshot/sign": {
      "post": {
        "operationId": "signScreenshotUrl",
        "summary": "Generate a signed screenshot URL",
        "description": "Creates a pre-signed URL that captures a screenshot when visited. The signed URL can be used without an API key, making it ideal for embedding in emails, documents, or sharing with third parties. Signing is free and does not count against your quota. Each render of the signed URL consumes one credit. URLs are tamper-proof and expire after the specified duration.",
        "x-openai-isConsequential": false,
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": ["url"],
                "properties": {
                  "url": { "type": "string", "description": "URL to capture (must be http or https)" },
                  "expires_in": { "type": "integer", "minimum": 60, "maximum": 2592000, "default": 86400, "description": "URL validity in seconds (default: 86400 = 1 day, max: 2592000 = 30 days)" },
                  "format": { "type": "string", "enum": ["png", "jpeg", "webp", "pdf"], "description": "Output format (default: png)" },
                  "width": { "type": "integer", "minimum": 320, "maximum": 3840, "description": "Viewport width (default: 1280)" },
                  "height": { "type": "integer", "minimum": 200, "maximum": 10000, "description": "Viewport height (default: 800)" },
                  "full_page": { "type": "boolean", "description": "Capture entire page (default: false)" },
                  "quality": { "type": "integer", "minimum": 1, "maximum": 100, "description": "Image quality (default: 90)" },
                  "delay": { "type": "integer", "minimum": 0, "maximum": 10000, "description": "Wait after load in ms (default: 0)" },
                  "dark_mode": { "type": "boolean", "description": "Dark mode (default: false)" },
                  "block_ads": { "type": "boolean", "description": "Block ads (default: true)" },
                  "block_cookie_banners": { "type": "boolean", "description": "Remove cookie banners (default: true)" },
                  "device": { "type": "string", "enum": ["iphone_14", "iphone_15_pro", "pixel_7", "ipad_pro", "macbook_pro"] },
                  "hide_selectors": { "type": "string", "description": "CSS selectors to hide" },
                  "click_selector": { "type": "string", "description": "CSS selector to click" },
                  "user_agent": { "type": "string", "description": "Custom user agent" }
                }
              },
              "examples": {
                "basic": {
                  "summary": "Basic signed URL (1 day)",
                  "value": { "url": "https://example.com" }
                },
                "custom": {
                  "summary": "Custom settings, 7 day expiry",
                  "value": { "url": "https://example.com", "expires_in": 604800, "format": "jpeg", "width": 1920, "dark_mode": true }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Signed URL generated",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "signed_url": { "type": "string", "description": "The pre-signed render URL" },
                    "expires_at": { "type": "string", "format": "date-time", "description": "When the URL expires" },
                    "expires_in": { "type": "integer", "description": "Validity duration in seconds" }
                  }
                },
                "example": {
                  "signed_url": "https://app.snap-render.com/v1/screenshot/render?url=https%3A%2F%2Fexample.com&expires=1775646672&key_prefix=sk_live_A1Lr&sig=e166b220...",
                  "expires_at": "2026-04-09T10:00:00.000Z",
                  "expires_in": 86400
                }
              }
            }
          },
          "400": { "description": "Invalid parameters or blocked URL", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } } },
          "401": { "description": "Invalid or missing API key", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } } }
        },
        "security": [{ "apiKey": [] }]
      }
    },
    "/v1/screenshot/render": {
      "get": {
        "operationId": "renderSignedUrl",
        "summary": "Render a signed screenshot URL",
        "description": "Renders a screenshot using a pre-signed URL. No API key required. The URL must have been generated via POST /v1/screenshot/sign. Expired URLs return 410 Gone, tampered URLs return 403 Forbidden.",
        "x-openai-isConsequential": false,
        "parameters": [
          { "name": "url", "in": "query", "required": true, "schema": { "type": "string" }, "description": "URL to capture" },
          { "name": "expires", "in": "query", "required": true, "schema": { "type": "string" }, "description": "Unix timestamp expiry" },
          { "name": "key_prefix", "in": "query", "required": true, "schema": { "type": "string" }, "description": "API key prefix for auth" },
          { "name": "sig", "in": "query", "required": true, "schema": { "type": "string" }, "description": "HMAC-SHA256 signature" }
        ],
        "responses": {
          "200": {
            "description": "Screenshot rendered",
            "content": {
              "image/png": { "schema": { "type": "string", "format": "binary" } },
              "image/jpeg": { "schema": { "type": "string", "format": "binary" } },
              "image/webp": { "schema": { "type": "string", "format": "binary" } },
              "application/pdf": { "schema": { "type": "string", "format": "binary" } }
            }
          },
          "403": { "description": "Invalid signature", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } } },
          "410": { "description": "Signed URL expired", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } } },
          "429": { "description": "Quota exceeded", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } } }
        },
        "security": []
      }
    },
    "/v1/extract": {
      "get": {
        "operationId": "extractContentGet",
        "summary": "Extract content from a web page (GET)",
        "description": "Extract structured content from any web page. Supports 6 extraction types: markdown (readable content via Readability), text (plain text), html (raw HTML), article (structured with title/author/excerpt), links (all page links), metadata (OG tags, title, description). Uses query parameters for GET requests.",
        "x-openai-isConsequential": false,
        "parameters": [
          { "name": "url", "in": "query", "required": true, "schema": { "type": "string" }, "description": "URL to extract content from" },
          { "name": "type", "in": "query", "schema": { "type": "string", "enum": ["markdown", "text", "html", "article", "links", "metadata"], "default": "markdown" }, "description": "Extraction type (default: markdown)" },
          { "name": "selector", "in": "query", "schema": { "type": "string" }, "description": "CSS selector to scope extraction" },
          { "name": "block_ads", "in": "query", "schema": { "type": "string", "enum": ["true", "false"], "default": "true" }, "description": "Block ads (default: true)" },
          { "name": "block_cookie_banners", "in": "query", "schema": { "type": "string", "enum": ["true", "false"], "default": "true" }, "description": "Remove cookie banners (default: true)" },
          { "name": "delay", "in": "query", "schema": { "type": "integer", "minimum": 0, "maximum": 10000, "default": 0 }, "description": "Wait after load in ms" },
          { "name": "max_length", "in": "query", "schema": { "type": "integer", "minimum": 1, "maximum": 500000, "default": 100000 }, "description": "Max content length in chars" },
          { "name": "cache", "in": "query", "schema": { "type": "string", "enum": ["true", "false"], "default": "false" }, "description": "Use cached result (default: false)" },
          { "name": "cache_ttl", "in": "query", "schema": { "type": "integer", "minimum": 0, "maximum": 2592000, "default": 86400 }, "description": "Cache TTL in seconds" }
        ],
        "responses": {
          "200": {
            "description": "Content extracted",
            "content": {
              "application/json": {
                "schema": { "$ref": "#/components/schemas/ExtractResult" },
                "example": {
                  "url": "https://example.com",
                  "type": "markdown",
                  "content": "# Example Domain\n\nThis domain is for use in documentation examples.",
                  "wordCount": 10,
                  "processingTimeMs": 1200
                }
              }
            }
          },
          "400": { "description": "Invalid parameters", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } } },
          "401": { "description": "Invalid or missing API key", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } } },
          "429": { "description": "Rate limit or quota exceeded", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } } }
        },
        "security": [{ "apiKey": [] }]
      },
      "post": {
        "operationId": "extractContentPost",
        "summary": "Extract content from a web page (POST)",
        "description": "Extract structured content from any web page using a JSON body. Supports the same 6 extraction types as GET. POST is recommended for programmatic use as it accepts native JSON types.",
        "x-openai-isConsequential": false,
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": ["url"],
                "properties": {
                  "url": { "type": "string", "description": "URL to extract content from" },
                  "type": { "type": "string", "enum": ["markdown", "text", "html", "article", "links", "metadata"], "default": "markdown", "description": "Extraction type" },
                  "selector": { "type": "string", "description": "CSS selector to scope extraction" },
                  "block_ads": { "type": "boolean", "default": true, "description": "Block ads" },
                  "block_cookie_banners": { "type": "boolean", "default": true, "description": "Remove cookie banners" },
                  "delay": { "type": "integer", "minimum": 0, "maximum": 10000, "default": 0, "description": "Wait after load in ms" },
                  "max_length": { "type": "integer", "minimum": 1, "maximum": 500000, "default": 100000, "description": "Max content length" },
                  "cache": { "type": "boolean", "default": false, "description": "Use cached result (default: false)" },
                  "cache_ttl": { "type": "integer", "minimum": 0, "maximum": 2592000, "default": 86400, "description": "Cache TTL in seconds" }
                }
              },
              "examples": {
                "markdown": {
                  "summary": "Extract as Markdown",
                  "value": { "url": "https://example.com", "type": "markdown" }
                },
                "article": {
                  "summary": "Extract article structure",
                  "value": { "url": "https://example.com/blog/post", "type": "article" }
                },
                "metadata": {
                  "summary": "Extract OG metadata",
                  "value": { "url": "https://example.com", "type": "metadata" }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Content extracted",
            "content": {
              "application/json": {
                "schema": { "$ref": "#/components/schemas/ExtractResult" }
              }
            }
          },
          "400": { "description": "Invalid parameters", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } } },
          "401": { "description": "Invalid or missing API key", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } } },
          "429": { "description": "Rate limit or quota exceeded", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } } }
        },
        "security": [{ "apiKey": [] }]
      }
    },
    "/v1/usage": {
      "get": {
        "operationId": "getUsage",
        "summary": "Get current month usage",
        "description": "Returns screenshot usage statistics for the current billing period including plan name, period dates, and remaining credits.",
        "x-openai-isConsequential": false,
        "responses": {
          "200": {
            "description": "Usage statistics",
            "content": {
              "application/json": {
                "schema": { "$ref": "#/components/schemas/Usage" }
              }
            }
          }
        },
        "security": [{ "apiKey": [] }]
      }
    },
    "/v1/usage/daily": {
      "get": {
        "operationId": "getDailyUsage",
        "summary": "Get daily usage breakdown",
        "description": "Returns daily screenshot counts for the past N days. Useful for monitoring usage trends and building dashboards.",
        "x-openai-isConsequential": false,
        "parameters": [
          {
            "name": "days",
            "in": "query",
            "schema": { "type": "integer", "minimum": 1, "maximum": 90, "default": 30 },
            "description": "Number of days to look back (1-90)",
            "example": 30
          }
        ],
        "responses": {
          "200": {
            "description": "Daily usage data",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "days": { "type": "integer" },
                    "data": {
                      "type": "array",
                      "items": {
                        "type": "object",
                        "properties": {
                          "date": { "type": "string", "format": "date" },
                          "count": { "type": "integer" }
                        }
                      }
                    }
                  }
                }
              }
            }
          }
        },
        "security": [{ "apiKey": [] }]
      }
    },
    "/v1/screenshot/batch": {
      "post": {
        "operationId": "createBatchJob",
        "summary": "Create a batch screenshot job",
        "description": "Submit up to 50 URLs for batch screenshot processing. Returns immediately with a job ID. Poll GET /v1/screenshot/batch/{jobId} for results. Each URL consumes one credit; failed URLs get credits rolled back automatically.",
        "x-openai-isConsequential": false,
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": ["urls"],
                "properties": {
                  "urls": { "type": "array", "items": { "type": "string" }, "minItems": 1, "maxItems": 50, "description": "Array of URLs to capture (1-50)" },
                  "format": { "type": "string", "enum": ["png", "jpeg", "webp", "pdf"], "default": "png" },
                  "width": { "type": "integer", "minimum": 320, "maximum": 3840, "default": 1280 },
                  "height": { "type": "integer", "minimum": 200, "maximum": 10000, "default": 800 },
                  "full_page": { "type": "boolean", "default": false },
                  "quality": { "type": "integer", "minimum": 1, "maximum": 100, "default": 90 },
                  "delay": { "type": "integer", "minimum": 0, "maximum": 10000, "default": 0 },
                  "dark_mode": { "type": "boolean", "default": false },
                  "block_ads": { "type": "boolean", "default": true },
                  "block_cookie_banners": { "type": "boolean", "default": true },
                  "hide_selectors": { "type": "string" },
                  "click_selector": { "type": "string" },
                  "device": { "type": "string", "enum": ["iphone_14", "iphone_15_pro", "pixel_7", "ipad_pro", "macbook_pro"] },
                  "user_agent": { "type": "string" }
                }
              }
            }
          }
        },
        "responses": {
          "202": {
            "description": "Batch job created",
            "content": {
              "application/json": {
                "schema": { "$ref": "#/components/schemas/BatchJobResponse" }
              }
            }
          },
          "400": { "description": "Invalid request", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } } },
          "401": { "description": "Unauthorized", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } } },
          "429": { "description": "Rate limited or quota exceeded", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } } }
        },
        "security": [{ "apiKey": [] }]
      }
    },
    "/v1/screenshot/batch/{jobId}": {
      "get": {
        "operationId": "getBatchJobStatus",
        "summary": "Get batch job status",
        "description": "Poll the status of a batch screenshot job. Returns current progress and download URLs for completed items. Download URLs are presigned and valid for 24 hours.",
        "parameters": [
          {
            "name": "jobId",
            "in": "path",
            "required": true,
            "schema": { "type": "string", "format": "uuid" },
            "description": "The batch job ID returned by POST /v1/screenshot/batch"
          }
        ],
        "responses": {
          "200": {
            "description": "Batch job status",
            "content": {
              "application/json": {
                "schema": { "$ref": "#/components/schemas/BatchJobResponse" }
              }
            }
          },
          "404": { "description": "Job not found or expired", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } } },
          "401": { "description": "Unauthorized", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } } }
        },
        "security": [{ "apiKey": [] }]
      }
    },
    "/v1/webhooks": {
      "post": {
        "operationId": "createWebhook",
        "summary": "Create a webhook",
        "description": "Register a webhook URL to receive event notifications. Max 5 webhooks per account. Payloads are signed with HMAC-SHA256 using the returned secret.",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": ["url", "events"],
                "properties": {
                  "url": { "type": "string", "format": "uri", "description": "HTTPS endpoint URL for webhook delivery" },
                  "events": { "type": "array", "items": { "type": "string", "enum": ["screenshot.completed", "quota.warning", "quota.exceeded"] }, "minItems": 1, "description": "Events to subscribe to" }
                }
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "Webhook created",
            "content": {
              "application/json": {
                "schema": { "$ref": "#/components/schemas/WebhookResponse" }
              }
            }
          },
          "400": { "description": "Invalid request or max webhooks reached", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } } },
          "401": { "description": "Unauthorized", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } } }
        },
        "security": [{ "apiKey": [] }]
      },
      "get": {
        "operationId": "listWebhooks",
        "summary": "List webhooks",
        "description": "List all webhooks for the authenticated account.",
        "responses": {
          "200": {
            "description": "List of webhooks",
            "content": {
              "application/json": {
                "schema": { "type": "array", "items": { "$ref": "#/components/schemas/WebhookResponse" } }
              }
            }
          },
          "401": { "description": "Unauthorized", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } } }
        },
        "security": [{ "apiKey": [] }]
      }
    },
    "/v1/webhooks/{webhookId}": {
      "delete": {
        "operationId": "deleteWebhook",
        "summary": "Delete a webhook",
        "description": "Remove a webhook. Future events will no longer be delivered to this URL.",
        "parameters": [
          { "name": "webhookId", "in": "path", "required": true, "schema": { "type": "string", "format": "uuid" } }
        ],
        "responses": {
          "204": { "description": "Webhook deleted" },
          "404": { "description": "Webhook not found", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } } },
          "401": { "description": "Unauthorized", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } } }
        },
        "security": [{ "apiKey": [] }]
      }
    },
    "/v1/webhooks/{webhookId}/test": {
      "post": {
        "operationId": "testWebhook",
        "summary": "Send test webhook delivery",
        "description": "Send a test payload to a webhook URL to verify it's working correctly.",
        "parameters": [
          { "name": "webhookId", "in": "path", "required": true, "schema": { "type": "string", "format": "uuid" } }
        ],
        "responses": {
          "200": {
            "description": "Test delivery result",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "deliveryId": { "type": "string" },
                    "statusCode": { "type": "integer", "nullable": true },
                    "success": { "type": "boolean" },
                    "deliveredAt": { "type": "string", "format": "date-time", "nullable": true }
                  }
                }
              }
            }
          },
          "404": { "description": "Webhook not found", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } } },
          "401": { "description": "Unauthorized", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } } }
        },
        "security": [{ "apiKey": [] }]
      }
    },
    "/health": {
      "get": {
        "operationId": "healthCheck",
        "summary": "Health check",
        "description": "Check API health including Chromium, Redis, and database connectivity. No authentication required.",
        "responses": {
          "200": {
            "description": "Service is healthy",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "status": { "type": "string" },
                    "checks": { "type": "object" }
                  }
                }
              }
            }
          }
        },
        "security": []
      }
    }
  },
  "components": {
    "securitySchemes": {
      "apiKey": {
        "type": "apiKey",
        "in": "header",
        "name": "X-API-Key",
        "description": "API key (starts with sk_live_). Get one free at https://app.snap-render.com/auth/signup"
      }
    },
    "schemas": {
      "Error": {
        "type": "object",
        "properties": {
          "error": {
            "type": "object",
            "properties": {
              "code": { "type": "string", "description": "Error code (e.g., INVALID_URL, RATE_LIMITED, QUOTA_EXCEEDED)" },
              "message": { "type": "string", "description": "Human-readable error description" },
              "status": { "type": "integer", "description": "HTTP status code" }
            }
          }
        }
      },
      "ExtractResult": {
        "type": "object",
        "properties": {
          "url": { "type": "string", "description": "The URL that was extracted" },
          "type": { "type": "string", "enum": ["markdown", "text", "html", "article", "links", "metadata"], "description": "Extraction type used" },
          "content": { "description": "Extracted content. String for markdown/text/html, object for article/metadata, array for links." },
          "wordCount": { "type": "integer", "description": "Word count (for text, markdown, article types)" },
          "processingTimeMs": { "type": "integer", "description": "Processing time in milliseconds" }
        }
      },
      "BatchJobResponse": {
        "type": "object",
        "properties": {
          "jobId": { "type": "string", "format": "uuid", "description": "Unique batch job identifier" },
          "status": { "type": "string", "enum": ["pending", "processing", "completed", "failed"], "description": "Overall job status" },
          "statusUrl": { "type": "string", "description": "URL to poll for status updates" },
          "total": { "type": "integer", "description": "Total number of URLs in the batch" },
          "completed": { "type": "integer", "description": "Number of successfully completed screenshots" },
          "failed": { "type": "integer", "description": "Number of failed screenshots" },
          "items": {
            "type": "array",
            "items": {
              "type": "object",
              "properties": {
                "url": { "type": "string", "description": "The URL that was captured" },
                "status": { "type": "string", "enum": ["pending", "completed", "failed"] },
                "downloadUrl": { "type": "string", "description": "Presigned download URL (24h validity, present when completed)" },
                "error": { "type": "string", "description": "Error message (present when failed)" }
              }
            }
          },
          "createdAt": { "type": "string", "format": "date-time" },
          "completedAt": { "type": "string", "format": "date-time", "description": "Present when job is completed or failed" }
        }
      },
      "WebhookResponse": {
        "type": "object",
        "properties": {
          "id": { "type": "string", "format": "uuid" },
          "url": { "type": "string", "format": "uri" },
          "events": { "type": "array", "items": { "type": "string", "enum": ["screenshot.completed", "quota.warning", "quota.exceeded"] } },
          "secret": { "type": "string", "description": "HMAC-SHA256 signing secret (whsec_ prefix). Store securely." },
          "isActive": { "type": "boolean" },
          "createdAt": { "type": "string", "format": "date-time" }
        }
      },
      "Usage": {
        "type": "object",
        "properties": {
          "plan": { "type": "string", "description": "Current plan name (free, growth, business, scale)" },
          "period": {
            "type": "object",
            "properties": {
              "start": { "type": "string", "format": "date-time" },
              "end": { "type": "string", "format": "date-time" }
            }
          },
          "usage": {
            "type": "object",
            "properties": {
              "screenshots_used": { "type": "integer", "description": "Screenshots used this period" },
              "screenshots_limit": { "type": "integer", "description": "Monthly screenshot limit for current plan" },
              "screenshots_remaining": { "type": "integer", "description": "Screenshots remaining this period" }
            }
          }
        }
      }
    }
  }
}
