{"openapi":"3.0.3","info":{"title":"shotpdf","version":"1.0.0","description":"Document-rendering API: URL screenshots, PDFs, and OG social images from one engine. Every /v1/* endpoint takes `Authorization: Bearer <key>` (keys are self-serve via Google login at https://astraeatech.dev). Free allows 1,000 metered requests/month, Pro 50,000; past the quota the API returns 429. Errors share one JSON envelope: `{\"error\": {\"code\": \"...\", \"message\": \"...\"}}`.","contact":{"email":"admin@astraeatech.dev"}},"servers":[{"url":"https://shotpdf.astraeatech.dev","description":"Production"}],"security":[{"bearerAuth":[]}],"tags":[{"name":"Renders","description":"Screenshot / PDF / OG rendering (authenticated, metered, cached)."},{"name":"Usage","description":"Key usage reporting (authenticated, never metered)."},{"name":"Meta","description":"Liveness and machine-readable descriptions (public)."}],"paths":{"/v1/screenshot":{"get":{"summary":"Screenshot a URL","description":"Render an http(s) URL in real Chromium and return a PNG or JPEG screenshot.","tags":["Renders"],"parameters":[{"name":"url","in":"query","required":true,"description":"Absolute http(s) URL to render. Localhost, cloud-metadata, and non-http(s) targets are rejected (400 invalid_url).","schema":{"type":"string","format":"uri"}},{"name":"width","in":"query","description":"Viewport width in CSS pixels.","schema":{"type":"integer","minimum":1,"maximum":3840,"default":1200}},{"name":"height","in":"query","description":"Viewport height in CSS pixels.","schema":{"type":"integer","minimum":1,"maximum":2160,"default":800}},{"name":"full_page","in":"query","description":"Capture the full scrollable page instead of the viewport (true/false/1/0).","schema":{"type":"boolean","default":false}},{"name":"format","in":"query","description":"Image format.","schema":{"type":"string","enum":["png","jpeg"],"default":"png"}},{"name":"ttl","in":"query","description":"Freshness window in seconds; 0 disables all caching for the request.","schema":{"type":"integer","minimum":0,"maximum":604800,"default":86400}}],"responses":{"200":{"description":"Rendered image bytes.","headers":{"X-Cache":{"description":"`HIT` when served from the render cache, `MISS` on a fresh render.","schema":{"type":"string","enum":["HIT","MISS"]}},"X-Response-Ms":{"description":"Worker time spent on the request in milliseconds.","schema":{"type":"integer"}}},"content":{"image/png":{"schema":{"type":"string","format":"binary"}},"image/jpeg":{"schema":{"type":"string","format":"binary"}}}},"400":{"description":"Error envelope `{\"error\": {\"code\": \"invalid_url | invalid_option\", ...}}` — bad url or out-of-range option.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Error envelope `{\"error\": {\"code\": \"invalid_api_key\", ...}}` — missing, malformed, unknown, or revoked key; the only response that is never metered.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"422":{"description":"Error envelope `{\"error\": {\"code\": \"render_failed | render_timeout\", ...}}` — Chromium launch, navigation, or timeout failure inside the render.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Error envelope `{\"error\": {\"code\": \"quota_exceeded\", ...}}` — plan monthly cap reached (Free 1,000, Pro 50,000 metered requests); resets the first of next month UTC.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"headers":{"X-RateLimit-Limit":{"description":"Monthly metered-request cap for the key's plan.","schema":{"type":"integer"}},"X-RateLimit-Remaining":{"description":"Always 0 on a 429.","schema":{"type":"integer"}}}}}}},"/v1/pdf":{"get":{"summary":"Render a URL to PDF","description":"Print an http(s) URL to PDF with Chromium's print pipeline.","tags":["Renders"],"parameters":[{"name":"url","in":"query","required":true,"description":"Absolute http(s) URL to render. Localhost, cloud-metadata, and non-http(s) targets are rejected (400 invalid_url).","schema":{"type":"string","format":"uri"}},{"name":"format","in":"query","description":"Paper size (case-insensitive).","schema":{"type":"string","enum":["A4","Letter"],"default":"A4"}},{"name":"margin","in":"query","description":"Uniform page margin in millimetres (all four sides).","schema":{"type":"integer","minimum":0,"maximum":100,"default":10}},{"name":"print_bg","in":"query","description":"Keep background graphics (`printBackground` is accepted as an alias).","schema":{"type":"boolean","default":true}},{"name":"ttl","in":"query","description":"Freshness window in seconds; 0 disables all caching for the request.","schema":{"type":"integer","minimum":0,"maximum":604800,"default":86400}}],"responses":{"200":{"description":"Rendered PDF bytes.","headers":{"X-Cache":{"description":"`HIT` when served from the render cache, `MISS` on a fresh render.","schema":{"type":"string","enum":["HIT","MISS"]}},"X-Response-Ms":{"description":"Worker time spent on the request in milliseconds.","schema":{"type":"integer"}}},"content":{"application/pdf":{"schema":{"type":"string","format":"binary"}}}},"400":{"description":"Error envelope `{\"error\": {\"code\": \"invalid_url | invalid_option\", ...}}` — bad url or out-of-range option.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Error envelope `{\"error\": {\"code\": \"invalid_api_key\", ...}}` — missing, malformed, unknown, or revoked key; the only response that is never metered.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"422":{"description":"Error envelope `{\"error\": {\"code\": \"render_failed | render_timeout\", ...}}` — Chromium launch, navigation, or timeout failure inside the render.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Error envelope `{\"error\": {\"code\": \"quota_exceeded\", ...}}` — plan monthly cap reached (Free 1,000, Pro 50,000 metered requests); resets the first of next month UTC.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"headers":{"X-RateLimit-Limit":{"description":"Monthly metered-request cap for the key's plan.","schema":{"type":"integer"}},"X-RateLimit-Remaining":{"description":"Always 0 on a 429.","schema":{"type":"integer"}}}}}},"post":{"summary":"Render inline HTML to PDF","description":"Print an inline HTML document to PDF with Chromium's print pipeline.","tags":["Renders"],"requestBody":{"required":true,"description":"JSON object with the HTML source plus print options. Bodies over 256 KB are rejected (413).","content":{"application/json":{"schema":{"type":"object","required":["html"],"properties":{"html":{"type":"string","description":"Full HTML document to print (non-empty)."},"format":{"type":"string","enum":["A4","Letter"],"default":"A4","description":"Paper size."},"margin":{"type":"integer","minimum":0,"maximum":100,"default":10,"description":"Uniform page margin in millimetres."},"print_bg":{"type":"boolean","default":true,"description":"Keep background graphics."},"printBackground":{"type":"boolean","description":"Alias of `print_bg` (used only when `print_bg` is absent)."},"ttl":{"type":"integer","minimum":0,"maximum":604800,"default":86400,"description":"Freshness window in seconds; 0 disables caching."}}}}}},"responses":{"200":{"description":"Rendered PDF bytes.","headers":{"X-Cache":{"description":"`HIT` when served from the render cache, `MISS` on a fresh render.","schema":{"type":"string","enum":["HIT","MISS"]}},"X-Response-Ms":{"description":"Worker time spent on the request in milliseconds.","schema":{"type":"integer"}}},"content":{"application/pdf":{"schema":{"type":"string","format":"binary"}}}},"400":{"description":"Error envelope `{\"error\": {\"code\": \"invalid_body | invalid_option\", ...}}` — malformed JSON, missing html, or out-of-range option.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Error envelope `{\"error\": {\"code\": \"invalid_api_key\", ...}}` — missing, malformed, unknown, or revoked key; the only response that is never metered.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"413":{"description":"Error envelope `{\"error\": {\"code\": \"payload_too_large\", ...}}` — request body (or inline html) exceeds 256 KB.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"415":{"description":"Error envelope `{\"error\": {\"code\": \"unsupported_media_type\", ...}}` — Content-Type is not application/json.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"422":{"description":"Error envelope `{\"error\": {\"code\": \"render_failed | render_timeout\", ...}}` — Chromium launch, navigation, or timeout failure inside the render.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Error envelope `{\"error\": {\"code\": \"quota_exceeded\", ...}}` — plan monthly cap reached (Free 1,000, Pro 50,000 metered requests); resets the first of next month UTC.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"headers":{"X-RateLimit-Limit":{"description":"Monthly metered-request cap for the key's plan.","schema":{"type":"integer"}},"X-RateLimit-Remaining":{"description":"Always 0 on a 429.","schema":{"type":"integer"}}}}}}},"/v1/og":{"get":{"summary":"Render an OG social image","description":"Return a 1200x630 JPEG (quality 90). Two modes: pass `title` for the built-in template, or `url` to capture a page.","tags":["Renders"],"parameters":[{"name":"title","in":"query","description":"Headline for the built-in template (template mode). Required unless `url` is given.","schema":{"type":"string","maxLength":300}},{"name":"subtitle","in":"query","description":"Sub-headline for the built-in template (template mode only).","schema":{"type":"string","maxLength":300}},{"name":"url","in":"query","description":"Page to capture (url mode). Required when `title` is absent.","schema":{"type":"string","format":"uri"}},{"name":"theme","in":"query","description":"Template theme (template mode).","schema":{"type":"string","enum":["light","dark"],"default":"light"}},{"name":"ttl","in":"query","description":"Freshness window in seconds; 0 disables all caching for the request.","schema":{"type":"integer","minimum":0,"maximum":604800,"default":86400}}],"responses":{"200":{"description":"1200x630 JPEG bytes.","headers":{"X-Cache":{"description":"`HIT` when served from the render cache, `MISS` on a fresh render.","schema":{"type":"string","enum":["HIT","MISS"]}},"X-Response-Ms":{"description":"Worker time spent on the request in milliseconds.","schema":{"type":"integer"}}},"content":{"image/jpeg":{"schema":{"type":"string","format":"binary"}}}},"400":{"description":"Error envelope `{\"error\": {\"code\": \"invalid_url | invalid_option\", ...}}` — neither title nor url, bad url, or over-long text.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Error envelope `{\"error\": {\"code\": \"invalid_api_key\", ...}}` — missing, malformed, unknown, or revoked key; the only response that is never metered.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"422":{"description":"Error envelope `{\"error\": {\"code\": \"render_failed | render_timeout\", ...}}` — Chromium launch, navigation, or timeout failure inside the render.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Error envelope `{\"error\": {\"code\": \"quota_exceeded\", ...}}` — plan monthly cap reached (Free 1,000, Pro 50,000 metered requests); resets the first of next month UTC.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"headers":{"X-RateLimit-Limit":{"description":"Monthly metered-request cap for the key's plan.","schema":{"type":"integer"}},"X-RateLimit-Remaining":{"description":"Always 0 on a 429.","schema":{"type":"integer"}}}}}}},"/v1/usage":{"get":{"summary":"Report key usage","description":"Metered totals and daily counts for the caller's key. Deliberately unmetered.","tags":["Usage"],"parameters":[{"name":"days","in":"query","description":"Trailing window in days.","schema":{"type":"integer","minimum":1,"maximum":365,"default":30}}],"responses":{"200":{"description":"Usage report for the key.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/UsageReport"}}}},"400":{"description":"Error envelope `{\"error\": {\"code\": \"invalid_option\", ...}}` — days outside 1-365.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Error envelope `{\"error\": {\"code\": \"invalid_api_key\", ...}}` — missing, malformed, unknown, or revoked key; the only response that is never metered.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/healthz":{"get":{"summary":"Liveness probe","description":"Unauthenticated, unmetered liveness check.","tags":["Meta"],"security":[{}],"responses":{"200":{"description":"Worker is alive.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Health"}}}}}}},"/openapi.json":{"get":{"summary":"OpenAPI document","description":"This machine-readable API description (unauthenticated, unmetered).","tags":["Meta"],"security":[{}],"responses":{"200":{"description":"OpenAPI 3.0.3 document.","content":{"application/json":{"schema":{"type":"object","description":"OpenAPI 3.0.3 document."}}}}}}}},"components":{"securitySchemes":{"bearerAuth":{"type":"http","scheme":"bearer","bearerFormat":"sk_live_<32 hex>","description":"API key minted on your profile (https://shotpdf.astraeatech.dev/profile). Send as `Authorization: Bearer <key>`."}},"schemas":{"Error":{"type":"object","required":["error"],"properties":{"error":{"type":"object","required":["code","message"],"properties":{"code":{"type":"string"},"message":{"type":"string"}}}}},"UsageReport":{"type":"object","required":["totals","daily"],"properties":{"totals":{"$ref":"#/components/schemas/UsageTotals"},"daily":{"type":"array","items":{"$ref":"#/components/schemas/DailyUsage"}}}},"UsageTotals":{"type":"object","required":["screenshot","pdf","og","cache_hit","error"],"properties":{"screenshot":{"type":"integer","description":"Screenshot renders (all statuses)."},"pdf":{"type":"integer","description":"PDF renders (all statuses)."},"og":{"type":"integer","description":"OG renders (all statuses)."},"cache_hit":{"type":"integer","description":"Cache hits across endpoints."},"error":{"type":"integer","description":"Errors across endpoints."}}},"DailyUsage":{"type":"object","required":["date","count"],"properties":{"date":{"type":"string","format":"date","description":"UTC day (days with no events are omitted)."},"count":{"type":"integer","description":"Total events that day."}}},"Health":{"type":"object","required":["ok"],"properties":{"ok":{"type":"boolean"}}}}}}