{
  "openapi": "3.1.0",
  "info": {
    "title": "yolopush-md",
    "version": "2.1.0",
    "description": "URL-to-Markdown and scroll-video API. Each destination and redirect hop is DNS-validated against private and special-use networks; pages can be scraped into LLM-ready Markdown or asynchronously rendered as smooth, speed-controlled MP4 videos."
  },
  "servers": [
    {
      "url": "https://md.yolopush.com",
      "description": "Production server"
    }
  ],
  "components": {
    "securitySchemes": {
      "ScrollVideoBearer": {
        "type": "http",
        "scheme": "bearer",
        "description": "API key configured as the SCROLL_VIDEO_API_KEY Worker secret."
      }
    },
    "parameters": {
      "ReturnFormat": {
        "name": "X-Return-Format",
        "in": "header",
        "description": "Format of the response. Use \"screenshot\" for a PNG image, \"html\" for raw HTML, \"text\" for plain text, or \"markdown\" (default).",
        "schema": {
          "type": "string",
          "enum": [
            "markdown",
            "html",
            "text",
            "screenshot"
          ],
          "default": "markdown"
        }
      },
      "NoCache": {
        "name": "X-No-Cache",
        "in": "header",
        "description": "Bypass the cache if set to \"true\".",
        "schema": {
          "type": "string",
          "enum": [
            "true",
            "false"
          ]
        }
      },
      "TargetSelector": {
        "name": "X-Target-Selector",
        "in": "header",
        "description": "CSS selector to extract a specific element from the page.",
        "schema": {
          "type": "string"
        }
      },
      "WaitForSelector": {
        "name": "X-Wait-For-Selector",
        "in": "header",
        "description": "Wait for this CSS selector to appear before scraping.",
        "schema": {
          "type": "string"
        }
      },
      "RemoveSelector": {
        "name": "X-Remove-Selector",
        "in": "header",
        "description": "CSS selector to remove from the page before scraping.",
        "schema": {
          "type": "string"
        }
      },
      "WithLinksSummary": {
        "name": "X-With-Links-Summary",
        "in": "header",
        "description": "Append a list of links found on the page to the output.",
        "schema": {
          "type": "string",
          "enum": [
            "none",
            "all",
            "dedup",
            "true"
          ],
          "default": "none"
        }
      },
      "WithImagesSummary": {
        "name": "X-With-Images-Summary",
        "in": "header",
        "description": "Append a list of images found on the page to the output.",
        "schema": {
          "type": "string",
          "enum": [
            "none",
            "all",
            "dedup",
            "true"
          ],
          "default": "none"
        }
      },
      "RetainImages": {
        "name": "X-Retain-Images",
        "in": "header",
        "description": "If set to \"none\", removes images from the markdown output.",
        "schema": {
          "type": "string",
          "default": "true"
        }
      },
      "TokenBudget": {
        "name": "X-Token-Budget",
        "in": "header",
        "description": "Truncate the output to fit approximately within this token budget (1 token ≈ 4 chars).",
        "schema": {
          "type": "integer",
          "minimum": 100
        }
      },
      "SetCookie": {
        "name": "X-Set-Cookie",
        "in": "header",
        "description": "Cookie string to forward to the target website during scraping.",
        "schema": {
          "type": "string"
        }
      },
      "Timeout": {
        "name": "X-Timeout",
        "in": "header",
        "description": "Timeout for the scraping request in seconds.",
        "schema": {
          "type": "integer",
          "minimum": 5,
          "maximum": 60,
          "default": 30
        }
      },
      "MdLinkStyle": {
        "name": "X-Md-Link-Style",
        "in": "header",
        "description": "Formatting style for markdown links.",
        "schema": {
          "type": "string",
          "enum": [
            "inline",
            "referenced",
            "discarded"
          ],
          "default": "inline"
        }
      },
      "Engine": {
        "name": "X-Engine",
        "in": "header",
        "description": "Specify the scraping engine to use.",
        "schema": {
          "type": "string",
          "enum": [
            "auto",
            "browser",
            "direct"
          ],
          "default": "auto"
        }
      },
      "ViewportWidth": {
        "name": "X-Viewport-Width",
        "in": "header",
        "description": "Viewport width for browser rendering or screenshots.",
        "schema": {
          "type": "integer"
        }
      },
      "ViewportHeight": {
        "name": "X-Viewport-Height",
        "in": "header",
        "description": "Viewport height for browser rendering or screenshots.",
        "schema": {
          "type": "integer"
        }
      },
      "AIImageDescriptionLanguage": {
        "name": "X-AI-Image-Description-Language",
        "in": "header",
        "description": "Language code for AI image descriptions (e.g., \"en\", \"es\").",
        "schema": {
          "type": "string",
          "enum": [
            "en",
            "it",
            "de",
            "es",
            "fr",
            "pt"
          ]
        }
      },
      "AIHTMLHostname": {
        "name": "X-AI-HTML-Hostname",
        "in": "header",
        "description": "Hostname to resolve relative links in HTML during AI conversion.",
        "schema": {
          "type": "string"
        }
      },
      "AIPDFMetadata": {
        "name": "X-AI-PDF-Metadata",
        "in": "header",
        "description": "Include PDF metadata block in the output.",
        "schema": {
          "type": "string",
          "enum": [
            "true",
            "false"
          ],
          "default": "false"
        }
      },
      "Accept": {
        "name": "Accept",
        "in": "header",
        "description": "Set to \"application/json\" to receive the output as a JSON object instead of raw text.",
        "schema": {
          "type": "string",
          "enum": [
            "application/json",
            "text/html",
            "text/plain",
            "text/markdown"
          ]
        }
      }
    },
    "schemas": {
      "JsonResponse": {
        "type": "object",
        "properties": {
          "success": {
            "type": "boolean",
            "example": true
          },
          "requestedUrl": {
            "type": "string",
            "format": "uri",
            "example": "https://example.com"
          },
          "url": {
            "type": "string",
            "format": "uri",
            "example": "https://www.example.com/"
          },
          "finalUrl": {
            "type": "string",
            "format": "uri",
            "example": "https://www.example.com/"
          },
          "canonicalUrl": {
            "type": "string",
            "format": "uri",
            "nullable": true
          },
          "httpStatus": {
            "type": "integer",
            "nullable": true,
            "example": 200
          },
          "fetchedAt": {
            "type": "string",
            "format": "date-time"
          },
          "readerVersion": {
            "type": "string",
            "example": "2"
          },
          "redirectPolicy": {
            "type": "string",
            "enum": [
              "manual-validated"
            ]
          },
          "provider": {
            "type": "string",
            "example": "ai-enhanced"
          },
          "durationMs": {
            "type": "integer",
            "example": 1234
          },
          "title": {
            "type": "string",
            "nullable": true,
            "example": "Example Domain"
          },
          "description": {
            "type": "string",
            "nullable": true,
            "example": "This domain is for use in illustrative examples in documents."
          },
          "content": {
            "type": "string",
            "example": "# Example Domain\n\nThis domain is for use in illustrative examples in documents."
          },
          "coverUrl": {
            "type": "string",
            "nullable": true,
            "format": "uri"
          },
          "images": {
            "type": "array",
            "items": {
              "type": "object",
              "properties": {
                "src": {
                  "type": "string",
                  "format": "uri"
                },
                "alt": {
                  "type": "string"
                }
              }
            }
          },
          "links": {
            "type": "array",
            "items": {
              "type": "object",
              "properties": {
                "href": {
                  "type": "string",
                  "format": "uri"
                },
                "text": {
                  "type": "string"
                }
              }
            }
          },
          "tokens": {
            "type": "integer",
            "nullable": true,
            "example": 56
          }
        },
        "required": [
          "success",
          "requestedUrl",
          "url",
          "finalUrl",
          "httpStatus",
          "fetchedAt",
          "readerVersion",
          "redirectPolicy",
          "provider",
          "durationMs",
          "content",
          "images",
          "links"
        ]
      },
      "ErrorResponse": {
        "type": "object",
        "properties": {
          "success": {
            "type": "boolean",
            "example": false
          },
          "error": {
            "type": "string",
            "example": "Missing URL. Usage: GET md.yolopush.com/<url> or POST { \"url\": \"...\" }"
          },
          "errorCode": {
            "type": "string",
            "description": "Stable URL-safety or fetch failure code when available."
          },
          "docs": {
            "type": "string",
            "format": "uri",
            "example": "https://yolopush.com/docs/md"
          },
          "url": {
            "type": "string",
            "format": "uri"
          },
          "durationMs": {
            "type": "integer"
          }
        },
        "required": [
          "success",
          "error"
        ]
      },
      "PostRequest": {
        "type": "object",
        "properties": {
          "url": {
            "type": "string",
            "format": "uri",
            "description": "The URL to scrape",
            "example": "https://example.com"
          }
        },
        "required": [
          "url"
        ]
      },
      "ScrollVideoCreateRequest": {
        "type": "object",
        "additionalProperties": false,
        "properties": {
          "url": {
            "type": "string",
            "format": "uri",
            "maxLength": 2048,
            "example": "https://example.com"
          },
          "speed": {
            "description": "A preset or an explicit pixels-per-second value.",
            "oneOf": [
              {
                "type": "string",
                "enum": [
                  "slow",
                  "normal",
                  "fast"
                ],
                "default": "normal"
              },
              {
                "type": "integer",
                "minimum": 80,
                "maximum": 800,
                "example": 320
              }
            ]
          },
          "maxDurationSeconds": {
            "type": "integer",
            "minimum": 2,
            "maximum": 30,
            "default": 20,
            "description": "Maximum output duration. Long pages are truncated; the selected speed is never accelerated."
          },
          "viewport": {
            "type": "object",
            "additionalProperties": false,
            "properties": {
              "width": {
                "type": "integer",
                "minimum": 320,
                "maximum": 1920,
                "default": 1280
              },
              "height": {
                "type": "integer",
                "minimum": 320,
                "maximum": 1080,
                "default": 720
              }
            }
          },
          "waitAfterLoadMs": {
            "type": "integer",
            "minimum": 500,
            "maximum": 10000,
            "default": 2000
          }
        },
        "required": [
          "url"
        ]
      },
      "ScrollVideoJob": {
        "type": "object",
        "properties": {
          "success": {
            "type": "boolean",
            "const": true
          },
          "jobId": {
            "type": "string",
            "format": "uuid"
          },
          "status": {
            "type": "string",
            "enum": [
              "queued",
              "processing",
              "ready",
              "failed"
            ]
          },
          "request": {
            "type": "object"
          },
          "createdAt": {
            "type": "string",
            "format": "date-time"
          },
          "updatedAt": {
            "type": "string",
            "format": "date-time"
          },
          "expiresAt": {
            "type": "string",
            "format": "date-time"
          },
          "attempts": {
            "type": "integer",
            "minimum": 0
          },
          "statusUrl": {
            "type": "string",
            "format": "uri"
          },
          "videoUrl": {
            "type": "string",
            "format": "uri"
          },
          "result": {
            "type": "object",
            "properties": {
              "contentType": {
                "type": "string",
                "const": "video/mp4"
              },
              "sizeBytes": {
                "type": "integer",
                "minimum": 1
              },
              "durationSeconds": {
                "type": "number",
                "exclusiveMinimum": 0
              },
              "scrollDistancePixels": {
                "type": "integer",
                "minimum": 0
              },
              "reachedBottom": {
                "type": "boolean"
              },
              "frameCount": {
                "type": "integer",
                "minimum": 1
              },
              "captureMs": {
                "type": "integer",
                "minimum": 0
              },
              "encodeMs": {
                "type": "integer",
                "minimum": 0
              }
            }
          },
          "error": {
            "type": "object",
            "properties": {
              "code": {
                "type": "string"
              },
              "message": {
                "type": "string"
              }
            }
          }
        },
        "required": [
          "success",
          "jobId",
          "status",
          "request",
          "createdAt",
          "updatedAt",
          "expiresAt",
          "attempts",
          "statusUrl"
        ]
      }
    },
    "responses": {
      "ErrorBadRequest": {
        "description": "Bad Request",
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/ErrorResponse"
            }
          }
        }
      },
      "ErrorServerError": {
        "description": "Server Error",
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/ErrorResponse"
            }
          }
        }
      },
      "ErrorRejectedTarget": {
        "description": "Target rejected by the public-network safety policy",
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/ErrorResponse"
            }
          }
        }
      },
      "ErrorTooLarge": {
        "description": "Target response exceeded the bounded reader limit",
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/ErrorResponse"
            }
          }
        }
      },
      "ErrorUnprocessable": {
        "description": "Destination or redirect chain could not be safely resolved",
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/ErrorResponse"
            }
          }
        }
      },
      "ErrorTimeout": {
        "description": "Destination or rendering request timed out",
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/ErrorResponse"
            }
          }
        }
      },
      "ScrapeResponse": {
        "description": "Successful scrape response. Content type depends on Accept and X-Return-Format headers.",
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/JsonResponse"
            }
          },
          "text/markdown": {
            "schema": {
              "type": "string"
            }
          },
          "text/html": {
            "schema": {
              "type": "string"
            }
          },
          "text/plain": {
            "schema": {
              "type": "string"
            }
          },
          "image/png": {
            "schema": {
              "type": "string",
              "format": "binary"
            }
          }
        },
        "headers": {
          "X-Provider": {
            "description": "Which scraping provider was used to retrieve the content.",
            "schema": {
              "type": "string"
            }
          },
          "X-Duration-Ms": {
            "description": "Time taken to scrape the URL in milliseconds.",
            "schema": {
              "type": "integer"
            }
          },
          "X-Markdown-Tokens": {
            "description": "Approximate number of tokens in the markdown output.",
            "schema": {
              "type": "integer"
            }
          },
          "X-Final-Url": {
            "description": "Verified final URL for screenshot responses.",
            "schema": {
              "type": "string",
              "format": "uri"
            }
          }
        }
      }
    }
  },
  "paths": {
    "/v1/scroll-videos": {
      "post": {
        "summary": "Create a scrolling video",
        "description": "Queues a live browser capture and returns immediately. Poll the returned statusUrl until status is ready, then GET videoUrl with the same Bearer token.",
        "security": [
          {
            "ScrollVideoBearer": []
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/ScrollVideoCreateRequest"
              }
            }
          }
        },
        "responses": {
          "202": {
            "description": "Render queued",
            "headers": {
              "Location": {
                "schema": {
                  "type": "string",
                  "format": "uri"
                }
              },
              "Retry-After": {
                "schema": {
                  "type": "integer",
                  "example": 2
                }
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ScrollVideoJob"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/ErrorBadRequest"
          },
          "401": {
            "description": "Invalid or missing Bearer token"
          },
          "413": {
            "description": "Request body too large"
          },
          "415": {
            "description": "Content-Type must be application/json"
          },
          "503": {
            "$ref": "#/components/responses/ErrorServerError"
          }
        }
      }
    },
    "/v1/scroll-videos/{jobId}": {
      "get": {
        "summary": "Get scrolling-video status",
        "security": [
          {
            "ScrollVideoBearer": []
          }
        ],
        "parameters": [
          {
            "name": "jobId",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Current job status",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ScrollVideoJob"
                }
              }
            }
          },
          "401": {
            "description": "Invalid or missing Bearer token"
          },
          "404": {
            "description": "Job not found"
          },
          "410": {
            "description": "Job expired"
          }
        }
      }
    },
    "/v1/scroll-videos/{jobId}/video": {
      "get": {
        "summary": "Stream a generated scrolling video",
        "description": "Streams the MP4 and supports HTTP byte-range requests.",
        "security": [
          {
            "ScrollVideoBearer": []
          }
        ],
        "parameters": [
          {
            "name": "jobId",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          },
          {
            "name": "Range",
            "in": "header",
            "schema": {
              "type": "string",
              "example": "bytes=0-1048575"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Complete MP4",
            "content": {
              "video/mp4": {
                "schema": {
                  "type": "string",
                  "format": "binary"
                }
              }
            }
          },
          "206": {
            "description": "Partial MP4 byte range",
            "content": {
              "video/mp4": {
                "schema": {
                  "type": "string",
                  "format": "binary"
                }
              }
            }
          },
          "401": {
            "description": "Invalid or missing Bearer token"
          },
          "404": {
            "description": "Job or MP4 not found"
          },
          "409": {
            "description": "Job is not ready"
          },
          "410": {
            "description": "Job expired"
          }
        }
      }
    },
    "/{url}": {
      "get": {
        "summary": "Scrape URL via GET",
        "description": "Scrapes the target URL and returns the content in the specified format.",
        "parameters": [
          {
            "name": "url",
            "in": "path",
            "required": true,
            "description": "The target URL to scrape (e.g. https://example.com)",
            "schema": {
              "type": "string"
            }
          },
          {
            "$ref": "#/components/parameters/Accept"
          },
          {
            "$ref": "#/components/parameters/ReturnFormat"
          },
          {
            "$ref": "#/components/parameters/NoCache"
          },
          {
            "$ref": "#/components/parameters/TargetSelector"
          },
          {
            "$ref": "#/components/parameters/WaitForSelector"
          },
          {
            "$ref": "#/components/parameters/RemoveSelector"
          },
          {
            "$ref": "#/components/parameters/WithLinksSummary"
          },
          {
            "$ref": "#/components/parameters/WithImagesSummary"
          },
          {
            "$ref": "#/components/parameters/RetainImages"
          },
          {
            "$ref": "#/components/parameters/TokenBudget"
          },
          {
            "$ref": "#/components/parameters/SetCookie"
          },
          {
            "$ref": "#/components/parameters/Timeout"
          },
          {
            "$ref": "#/components/parameters/MdLinkStyle"
          },
          {
            "$ref": "#/components/parameters/Engine"
          },
          {
            "$ref": "#/components/parameters/ViewportWidth"
          },
          {
            "$ref": "#/components/parameters/ViewportHeight"
          },
          {
            "$ref": "#/components/parameters/AIImageDescriptionLanguage"
          },
          {
            "$ref": "#/components/parameters/AIHTMLHostname"
          },
          {
            "$ref": "#/components/parameters/AIPDFMetadata"
          }
        ],
        "responses": {
          "200": {
            "$ref": "#/components/responses/ScrapeResponse"
          },
          "400": {
            "$ref": "#/components/responses/ErrorBadRequest"
          },
          "403": {
            "$ref": "#/components/responses/ErrorRejectedTarget"
          },
          "413": {
            "$ref": "#/components/responses/ErrorTooLarge"
          },
          "422": {
            "$ref": "#/components/responses/ErrorUnprocessable"
          },
          "500": {
            "$ref": "#/components/responses/ErrorServerError"
          },
          "501": {
            "$ref": "#/components/responses/ErrorServerError"
          },
          "502": {
            "$ref": "#/components/responses/ErrorServerError"
          },
          "504": {
            "$ref": "#/components/responses/ErrorTimeout"
          }
        }
      }
    },
    "/": {
      "post": {
        "summary": "Scrape URL via POST",
        "description": "Scrapes the target URL specified in the JSON body and returns the content in the specified format.",
        "parameters": [
          {
            "$ref": "#/components/parameters/Accept"
          },
          {
            "$ref": "#/components/parameters/ReturnFormat"
          },
          {
            "$ref": "#/components/parameters/NoCache"
          },
          {
            "$ref": "#/components/parameters/TargetSelector"
          },
          {
            "$ref": "#/components/parameters/WaitForSelector"
          },
          {
            "$ref": "#/components/parameters/RemoveSelector"
          },
          {
            "$ref": "#/components/parameters/WithLinksSummary"
          },
          {
            "$ref": "#/components/parameters/WithImagesSummary"
          },
          {
            "$ref": "#/components/parameters/RetainImages"
          },
          {
            "$ref": "#/components/parameters/TokenBudget"
          },
          {
            "$ref": "#/components/parameters/SetCookie"
          },
          {
            "$ref": "#/components/parameters/Timeout"
          },
          {
            "$ref": "#/components/parameters/MdLinkStyle"
          },
          {
            "$ref": "#/components/parameters/Engine"
          },
          {
            "$ref": "#/components/parameters/ViewportWidth"
          },
          {
            "$ref": "#/components/parameters/ViewportHeight"
          },
          {
            "$ref": "#/components/parameters/AIImageDescriptionLanguage"
          },
          {
            "$ref": "#/components/parameters/AIHTMLHostname"
          },
          {
            "$ref": "#/components/parameters/AIPDFMetadata"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/PostRequest"
              }
            }
          }
        },
        "responses": {
          "200": {
            "$ref": "#/components/responses/ScrapeResponse"
          },
          "400": {
            "$ref": "#/components/responses/ErrorBadRequest"
          },
          "403": {
            "$ref": "#/components/responses/ErrorRejectedTarget"
          },
          "413": {
            "$ref": "#/components/responses/ErrorTooLarge"
          },
          "422": {
            "$ref": "#/components/responses/ErrorUnprocessable"
          },
          "500": {
            "$ref": "#/components/responses/ErrorServerError"
          },
          "501": {
            "$ref": "#/components/responses/ErrorServerError"
          },
          "502": {
            "$ref": "#/components/responses/ErrorServerError"
          },
          "504": {
            "$ref": "#/components/responses/ErrorTimeout"
          }
        }
      }
    }
  }
}