{"openapi":"3.1.0","info":{"title":"pdfops API","version":"1.0.0","description":"PDF infrastructure as a service. Render HTML or URLs to pixel-perfect PDFs with a single API call. Supports async rendering, webhooks, content caching, and idempotency.","contact":{"name":"pdfops support","email":"support@pdfops.io"}},"servers":[{"url":"https://pdfops-api.avirajkhare00.workers.dev","description":"Production"}],"security":[{"bearerAuth":[]}],"components":{"securitySchemes":{"bearerAuth":{"type":"http","scheme":"bearer","description":"API key. Create keys via `POST /v1/keys`. Use `pdfops_live_` prefix for production and `pdfops_test_` for testing."}},"schemas":{"Error":{"type":"object","required":["error"],"properties":{"error":{"type":"object","required":["code","message"],"properties":{"code":{"type":"string","enum":["invalid_source","invalid_options","render_timeout","render_failed","rate_limited","unauthorized","not_found","idempotency_conflict","internal_error"]},"message":{"type":"string"}}}}},"RenderOptions":{"type":"object","properties":{"format":{"type":"string","enum":["A4","A3","Letter","Legal"],"default":"A4","description":"Page size."},"margin":{"type":"object","properties":{"top":{"type":"string","example":"20px"},"bottom":{"type":"string","example":"20px"},"left":{"type":"string","example":"20px"},"right":{"type":"string","example":"20px"}}},"landscape":{"type":"boolean","default":false},"scale":{"type":"number","minimum":0.1,"maximum":2,"default":1},"print_background":{"type":"boolean","default":true,"description":"Print background graphics."},"header_template":{"type":"string","description":"HTML template for the page header."},"footer_template":{"type":"string","description":"HTML template for the page footer."},"wait_until":{"type":"string","enum":["load","domcontentloaded","networkidle0","networkidle2"],"default":"networkidle0","description":"When to consider the page loaded."},"wait_for_selector":{"type":"string","description":"CSS selector to wait for before printing."},"headers":{"type":"object","additionalProperties":{"type":"string"},"description":"Extra HTTP request headers (URL source only)."},"timeout_ms":{"type":"integer","minimum":1000,"maximum":30000,"default":30000,"description":"Render timeout in milliseconds."}}},"RenderOutput":{"type":"object","required":["url","expires_at","sha256","bytes","pages"],"properties":{"url":{"type":"string","format":"uri","description":"Signed download URL (valid for 1 hour). Always fresh when fetched via `GET /v1/renders/{id}`."},"expires_at":{"type":"string","format":"date-time"},"sha256":{"type":"string","description":"SHA-256 hex digest of the normalized PDF bytes."},"bytes":{"type":"integer","description":"File size in bytes."},"pages":{"type":"integer","description":"Number of pages."}}},"Render":{"type":"object","required":["id","status","created_at"],"properties":{"id":{"type":"string","example":"rnd_abc123"},"status":{"type":"string","enum":["queued","rendering","completed","failed"]},"cache_hit":{"type":"boolean","description":"Present and `true` when the result was served from the content-addressed cache."},"output":{"oneOf":[{"$ref":"#/components/schemas/RenderOutput"},{"type":"null"}]},"error":{"oneOf":[{"type":"object","properties":{"code":{"type":"string"},"message":{"type":"string"}}},{"type":"null"}]},"created_at":{"type":"string","format":"date-time"},"completed_at":{"type":"string","format":"date-time","nullable":true}}},"ApiKey":{"type":"object","required":["id","name","environment","renders_today","created_at"],"properties":{"id":{"type":"string","example":"key_abc123"},"name":{"type":"string","example":"Production"},"environment":{"type":"string","enum":["live","test"]},"last_used_at":{"type":"string","format":"date-time","nullable":true},"renders_today":{"type":"integer","description":"Render count in the last 24 hours."},"created_at":{"type":"string","format":"date-time"}}},"CreatedApiKey":{"allOf":[{"$ref":"#/components/schemas/ApiKey"},{"type":"object","required":["key"],"properties":{"key":{"type":"string","example":"pdfops_live_abc123","description":"Raw API key value. Shown **only once** at creation. Store it securely."}}}]}}},"paths":{"/v1/renders":{"post":{"operationId":"createRender","summary":"Create a render","description":"Render HTML or a URL to a PDF. Defaults to async — returns immediately with a `queued` status and a render ID to poll. Set `async: false` for synchronous rendering (max 30s).\n\n**Content cache**: Identical inputs (same HTML/URL + options + engine version) return instantly from cache. Pass `force_render: true` to bypass.\n\n**Idempotency**: Pass `Idempotency-Key` header to safely retry without double-rendering.","tags":["Renders"],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["source"],"properties":{"source":{"oneOf":[{"type":"object","required":["type","html"],"properties":{"type":{"type":"string","enum":["html"]},"html":{"type":"string","maxLength":10485760,"description":"HTML content to render."},"css":{"type":"string","description":"Additional CSS injected into the page."}}},{"type":"object","required":["type","url"],"properties":{"type":{"type":"string","enum":["url"]},"url":{"type":"string","format":"uri","description":"URL to render. Must be publicly reachable."}}}]},"options":{"$ref":"#/components/schemas/RenderOptions"},"async":{"type":"boolean","default":true,"description":"Return immediately with a queued render (true) or wait for completion (false)."},"force_render":{"type":"boolean","default":false,"description":"Bypass the content-addressed cache and always perform a fresh render."},"webhook_url":{"type":"string","format":"uri","description":"URL to notify on render completion or failure."}}},"examples":{"html_async":{"summary":"Async HTML render","value":{"source":{"type":"html","html":"<h1>Hello world</h1>"},"async":true}},"html_sync":{"summary":"Sync HTML render","value":{"source":{"type":"html","html":"<h1>Invoice #123</h1>"},"async":false,"options":{"format":"A4"}}},"url_render":{"summary":"URL render","value":{"source":{"type":"url","url":"https://example.com"},"async":true}},"force_render":{"summary":"Force re-render (bypass cache)","value":{"source":{"type":"html","html":"<h1>Always fresh</h1>"},"force_render":true}}}}}},"parameters":[{"name":"Idempotency-Key","in":"header","schema":{"type":"string"},"description":"Unique key (UUID recommended). Repeated requests with the same key and body return the original render without re-rendering."}],"responses":{"200":{"description":"Sync render completed, or cache hit.","headers":{"X-Cache":{"schema":{"type":"string","enum":["HIT"]},"description":"Present when result is from cache."},"X-RateLimit-Limit-RPM":{"schema":{"type":"integer"}},"X-RateLimit-Remaining-RPM":{"schema":{"type":"integer"}},"X-RateLimit-Limit-Daily":{"schema":{"type":"integer"}},"X-RateLimit-Remaining-Daily":{"schema":{"type":"integer"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Render"}}}},"202":{"description":"Async render queued.","content":{"application/json":{"schema":{"type":"object","properties":{"id":{"type":"string"},"status":{"type":"string","enum":["queued"]},"created_at":{"type":"string","format":"date-time"}}}}}},"400":{"description":"Invalid request.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Unauthorized.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"409":{"description":"Idempotency key conflict.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Rate limit exceeded.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/v1/renders/{id}":{"get":{"operationId":"getRender","summary":"Get a render","description":"Fetch the current status of a render. For completed renders, always returns a **fresh 1-hour signed download URL** — never a stale link.","tags":["Renders"],"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"},"example":"rnd_abc123"}],"responses":{"200":{"description":"Render object.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Render"}}}},"401":{"description":"Unauthorized.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"Render not found.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/v1/renders/{id}/file":{"get":{"operationId":"downloadRender","summary":"Download a PDF","description":"Stream the rendered PDF. No API key required — access is controlled by the signed URL token (`expires` and `sig` query parameters) embedded in the download URL returned by `GET /v1/renders/{id}`.","tags":["Renders"],"security":[],"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"}},{"name":"expires","in":"query","required":true,"schema":{"type":"integer"},"description":"Expiry timestamp (ms since epoch)."},{"name":"sig","in":"query","required":true,"schema":{"type":"string"},"description":"HMAC-SHA256 signature."}],"responses":{"200":{"description":"PDF file stream.","content":{"application/pdf":{"schema":{"type":"string","format":"binary"}}}},"401":{"description":"Invalid or expired token.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"Render not found or not yet completed.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/v1/keys":{"get":{"operationId":"listKeys","summary":"List API keys","description":"Returns all active (non-revoked) API keys for the account.","tags":["API Keys"],"responses":{"200":{"description":"List of API keys.","content":{"application/json":{"schema":{"type":"object","properties":{"data":{"type":"array","items":{"$ref":"#/components/schemas/ApiKey"}}}}}}},"401":{"description":"Unauthorized.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}},"post":{"operationId":"createKey","summary":"Create an API key","description":"Create a new API key. The raw key value is returned **only once** — store it immediately in a secrets manager.","tags":["API Keys"],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["name"],"properties":{"name":{"type":"string","minLength":1,"maxLength":100,"example":"Production"},"environment":{"type":"string","enum":["live","test"],"default":"live"}}}}}},"responses":{"201":{"description":"Key created. The `key` field is only present in this response.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/CreatedApiKey"}}}},"400":{"description":"Invalid request.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Unauthorized.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/v1/keys/{id}":{"delete":{"operationId":"deleteKey","summary":"Revoke an API key","description":"Permanently revokes the key. All subsequent requests using this key will return 401. This action is irreversible.","tags":["API Keys"],"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"},"example":"key_abc123"}],"responses":{"204":{"description":"Key revoked."},"401":{"description":"Unauthorized.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"Key not found or already revoked.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/v1/auth/magic-link":{"post":{"operationId":"requestMagicLink","summary":"Request a magic link","description":"Send a login link to the given email address. The link expires in 15 minutes and can only be used once. No API key required.","tags":["Auth"],"security":[],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["email"],"properties":{"email":{"type":"string","format":"email"}}},"example":{"email":"you@example.com"}}}},"responses":{"200":{"description":"Email sent.","content":{"application/json":{"schema":{"type":"object","properties":{"message":{"type":"string"}}}}}},"400":{"description":"Invalid email.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"500":{"description":"Email delivery failed.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/v1/auth/verify":{"get":{"operationId":"verifyMagicLink","summary":"Verify a magic link","description":"Exchange a magic link token for an account and API key. Called automatically when the user clicks the login link. The token is single-use.","tags":["Auth"],"security":[],"parameters":[{"name":"token","in":"query","required":true,"schema":{"type":"string"},"description":"Raw token from the magic link URL."}],"responses":{"200":{"description":"Signed in. Returns account info and a new API key.","content":{"application/json":{"schema":{"type":"object","properties":{"message":{"type":"string"},"account":{"type":"object","properties":{"id":{"type":"string"},"email":{"type":"string"},"plan":{"type":"string"}}},"api_key":{"type":"object","properties":{"id":{"type":"string"},"key":{"type":"string","description":"Raw API key. Shown once — store immediately."},"note":{"type":"string"}}}}}}}},"401":{"description":"Invalid, expired, or already-used token.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/v1/webhooks":{"get":{"operationId":"listWebhooks","summary":"List webhook endpoints","tags":["Webhooks"],"responses":{"200":{"description":"List of webhook endpoints.","content":{"application/json":{"schema":{"type":"object","properties":{"data":{"type":"array","items":{"type":"object","properties":{"id":{"type":"string"},"url":{"type":"string","format":"uri"},"events":{"type":"array","items":{"type":"string"}},"created_at":{"type":"string","format":"date-time"}}}}}}}}}}},"post":{"operationId":"createWebhook","summary":"Create a webhook endpoint","description":"Register a URL to receive event notifications. A signing secret is returned **only at creation** — use it to verify the `X-Pdfops-Signature` header on incoming requests.","tags":["Webhooks"],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["url"],"properties":{"url":{"type":"string","format":"uri"},"events":{"type":"array","items":{"type":"string","enum":["render.completed","render.failed"]},"default":["render.completed","render.failed"]}}}}}},"responses":{"201":{"description":"Endpoint created.","content":{"application/json":{"schema":{"type":"object","properties":{"id":{"type":"string"},"url":{"type":"string"},"events":{"type":"array","items":{"type":"string"}},"secret":{"type":"string","description":"Signing secret. Shown once — store immediately."},"secret_note":{"type":"string"},"created_at":{"type":"string","format":"date-time"}}}}}},"400":{"description":"Invalid request.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Unauthorized.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/v1/webhooks/{id}":{"delete":{"operationId":"deleteWebhook","summary":"Delete a webhook endpoint","tags":["Webhooks"],"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"}}],"responses":{"204":{"description":"Deleted."},"401":{"description":"Unauthorized.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"Not found.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}}},"tags":[{"name":"Auth","description":"Magic link authentication."},{"name":"Renders","description":"Create and retrieve PDF renders."},{"name":"API Keys","description":"Manage API keys for your account."},{"name":"Webhooks","description":"Manage webhook endpoints."}]}