{"openapi":"3.1.0","info":{"title":"Grabbit Screenshot API","version":"1.0.0","summary":"Send a URL, get back a hosted screenshot.","description":"Grabbit renders any public web page in a real browser and returns a hosted PNG, JPEG, or WebP. Flat $0.002 per live grab; sk_test_ keys return free placeholder images so you can integrate before paying. Errors always use the envelope `{\"error\": {\"code\", \"message\"}}` with a stable machine-readable code.","contact":{"name":"Grabbit support","email":"support@grabbit.live","url":"https://www.grabbit.live/contact"},"termsOfService":"https://www.grabbit.live/terms"},"externalDocs":{"description":"API reference","url":"https://www.grabbit.live/docs"},"servers":[{"url":"https://api.grabbit.live/v1","description":"Production"},{"url":"https://www.grabbit.live/api/v1","description":"Production (same API on the web origin)"}],"tags":[{"name":"Grabs","description":"Capture and retrieve screenshots."},{"name":"Account","description":"Credits, usage, and pricing."}],"security":[{"bearerAuth":[]},{"oauth2":["grabs"]}],"paths":{"/grabs":{"post":{"operationId":"createGrab","summary":"Capture a screenshot of a URL","description":"Renders a public web page in a real Chromium browser and returns a hosted image URL. Synchronous by default (the render times out after 25 seconds). Send `Prefer: respond-async` or `?async=true` to get a 202 with a grab id, then poll GET /grabs/{id}. Live keys spend one credit per successful grab; sk_test_ keys return a free placeholder image.","tags":["Grabs"],"parameters":[{"name":"Idempotency-Key","in":"header","required":false,"description":"Optional. Retries with the same key return the original response.","schema":{"type":"string"}},{"name":"Prefer","in":"header","required":false,"description":"Optional. `respond-async` queues the grab and returns 202.","schema":{"type":"string"}},{"name":"async","in":"query","required":false,"description":"Optional. Same as `Prefer: respond-async`.","schema":{"type":"boolean"}}],"security":[{"bearerAuth":[]},{"oauth2":["grabs"]}],"x-codeSamples":[{"lang":"Shell","label":"curl","source":"curl https://api.grabbit.live/v1/grabs \\\n  -H \"Authorization: Bearer sk_test_...\" \\\n  -H \"Content-Type: application/json\" \\\n  -d '{\"url\": \"https://example.com\", \"full_page\": true}'"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CreateGrabRequest"}}}},"responses":{"200":{"description":"Captured. image_url is ready.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Grab"}}}},"202":{"description":"Queued (async). Poll GET /grabs/{id}.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Grab"}}}},"400":{"description":"Invalid input (`bad_request`, `invalid_viewport`, `ssrf_detected`, `idempotency_mismatch`).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Missing or invalid Bearer token (`unauthorized`).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"402":{"description":"No live credits left (`credits_exhausted`).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"409":{"description":"Same Idempotency-Key still in flight (`idempotency_conflict`).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"422":{"description":"The target site blocked the capture (`render_blocked`).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"`rate_limit_exceeded` or `queue_limit_exceeded`.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"500":{"description":"The page could not be rendered (`render_failed`, `internal_error`).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}},"get":{"operationId":"listGrabs","summary":"List recent grabs","description":"Lists the calling key's grabs, newest first, scoped to the key's team and environment.","tags":["Grabs"],"parameters":[{"name":"limit","in":"query","required":false,"description":"1 to 100. Default 25.","schema":{"type":"integer","minimum":1,"maximum":100,"default":25}},{"name":"status","in":"query","required":false,"description":"Optional status filter.","schema":{"type":"string","enum":["pending","processing","done","failed"]}}],"security":[{"bearerAuth":[]},{"oauth2":["grabs"]}],"x-codeSamples":[{"lang":"Shell","label":"curl","source":"curl \"https://api.grabbit.live/v1/grabs?limit=10\" -H \"Authorization: Bearer sk_test_...\""}],"responses":{"200":{"description":"A page of grabs, newest first.","content":{"application/json":{"schema":{"type":"object","required":["data","has_more"],"properties":{"data":{"type":"array","items":{"$ref":"#/components/schemas/Grab"}},"has_more":{"type":"boolean"}}}}}},"400":{"description":"Unknown status filter (`bad_request`).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Missing or invalid Bearer token (`unauthorized`).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/grabs/{id}":{"get":{"operationId":"getGrab","summary":"Fetch one grab","description":"Returns a single grab. Poll this after an async create until status is done or failed.","tags":["Grabs"],"parameters":[{"name":"id","in":"path","required":true,"description":"The grab id.","schema":{"type":"string","format":"uuid"}}],"security":[{"bearerAuth":[]},{"oauth2":["grabs"]}],"x-codeSamples":[{"lang":"Shell","label":"curl","source":"curl https://api.grabbit.live/v1/grabs/GRAB_ID -H \"Authorization: Bearer sk_test_...\""}],"responses":{"200":{"description":"The grab.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Grab"}}}},"401":{"description":"Missing or invalid Bearer token (`unauthorized`).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"No grab with that id for this key (`not_found`).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/usage":{"get":{"operationId":"getUsage","summary":"Get credit balance and usage","description":"Returns remaining credits (subscription + prepaid), the plan, and 30-day grab counts. Check this before a large batch so you can top up first.","tags":["Account"],"security":[{"bearerAuth":[]},{"oauth2":["grabs"]}],"x-codeSamples":[{"lang":"Shell","label":"curl","source":"curl https://api.grabbit.live/v1/usage -H \"Authorization: Bearer sk_live_...\""}],"responses":{"200":{"description":"Credits and usage.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Usage"}}}},"401":{"description":"Missing or invalid Bearer token (`unauthorized`).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/pricing":{"get":{"operationId":"getPricing","summary":"Get pricing and competitor comparison","description":"Public, no auth. Flat per-grab price, prepaid packages, and a dated comparison against other screenshot APIs.","tags":["Account"],"security":[],"x-codeSamples":[{"lang":"Shell","label":"curl","source":"curl https://api.grabbit.live/v1/pricing"}],"responses":{"200":{"description":"Pricing packet.","content":{"application/json":{"schema":{"type":"object"}}}}}}}},"components":{"securitySchemes":{"bearerAuth":{"type":"http","scheme":"bearer","description":"API key from https://grabbit.live/app/api-keys. sk_live_ keys render real screenshots and spend credits; sk_test_ keys return free placeholders."},"oauth2":{"type":"oauth2","description":"OAuth 2.1 authorization code + PKCE with dynamic client registration (RFC 7591). Used by MCP clients such as claude.ai.","flows":{"authorizationCode":{"authorizationUrl":"https://www.grabbit.live/oauth/authorize","tokenUrl":"https://www.grabbit.live/api/v1/oauth/token","refreshUrl":"https://www.grabbit.live/api/v1/oauth/token","scopes":{"grabs":"Create, list, and read screenshots and read the team's credit balance."}}}}},"schemas":{"Grab":{"type":"object","required":["id","status","created_at"],"properties":{"id":{"type":"string","format":"uuid","description":"Grab id."},"status":{"type":"string","enum":["pending","processing","done","failed"],"description":"Lifecycle state. Only done grabs have an image_url."},"target_url":{"type":"string","format":"uri","description":"The captured URL."},"image_url":{"type":["string","null"],"description":"Hosted image URL once status is done."},"width":{"type":["integer","null"],"description":"Viewport width in pixels."},"height":{"type":["integer","null"],"description":"Viewport height; null for full_page."},"format":{"type":"string","enum":["png","jpeg","webp"]},"bytes":{"type":["integer","null"],"description":"Image size in bytes."},"execution_ms":{"type":["integer","null"],"description":"Render time in milliseconds."},"error_message":{"type":["string","null"],"description":"Why a failed grab failed."},"created_at":{"type":"string","format":"date-time"}}},"CreateGrabRequest":{"type":"object","required":["url"],"properties":{"url":{"type":"string","format":"uri","description":"Absolute http(s) URL."},"width":{"type":"integer","minimum":320,"maximum":1920,"default":1280},"height":{"type":"integer","minimum":240,"maximum":1080,"default":720},"format":{"type":"string","enum":["png","jpeg","webp"],"default":"png"},"full_page":{"type":"boolean","default":false},"delay_ms":{"type":"integer","minimum":0,"maximum":10000,"default":0},"selector":{"type":["string","null"],"description":"CSS selector to capture one element (sync only)."}}},"Usage":{"type":"object","properties":{"credits":{"type":"object","properties":{"subscription":{"type":"integer"},"prepaid":{"type":"integer"},"total":{"type":"integer"}}},"plan":{"type":["string","null"]},"usage":{"type":"object","properties":{"grabs_last_30d":{"type":"integer"},"live_grabs_last_30d":{"type":"integer"}}},"environment":{"type":"string","enum":["live","test"]}}},"Error":{"type":"object","required":["error"],"properties":{"error":{"type":"object","required":["code","message"],"properties":{"code":{"type":"string","enum":["unauthorized","bad_request","invalid_viewport","ssrf_detected","credits_exhausted","not_found","idempotency_conflict","idempotency_mismatch","rate_limit_exceeded","queue_limit_exceeded","render_blocked","render_failed","internal_error"],"description":"Stable machine-readable code. Branch on this, not the message."},"message":{"type":"string","description":"Human-readable explanation."}}}}}}}}