{"openapi":"3.1.0","info":{"title":"Document Operation API","description":"Stateless document operations. Send markup or a file, get bytes back.\n\n**Nothing is stored.** Work happens in a tmpfs workspace that is unlinked before the\nresponse is sent. There is no object storage in the request path.\n\n**Credits are one-time and never expire.** No subscription, no feature gating between\ntiers — the only thing that differs between packs is volume. Every response carries an\n`X-Credits-Used` header, and a failed operation is refunded in full and automatically.\n\nAuthenticate with `X-API-Key: dk_live_...`. Errors always have the same shape: an `error`\nobject with a stable `code`. Branch on the code, never on the message.\n","license":{"name":"Proprietary"},"version":"0.1.0"},"servers":[{"url":"https://api.docufinery.com","description":"production"}],"paths":{"/health":{"get":{"tags":["ops"],"summary":"Health","description":"Process liveness. Never touches the database. See module docstring.","operationId":"getHealth","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"additionalProperties":true,"type":"object","title":"Response Health Health Get"}}}}}}},"/health/ready":{"get":{"tags":["ops"],"summary":"Ready","description":"Readiness: the database answers.","operationId":"getHealthReady","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}}}}},"/api/v1/auth/config":{"get":{"tags":["auth"],"summary":"Public sign-in configuration","description":"Which sign-in methods this deployment offers.\n\n**Fetched rather than baked into the static export.** A `NEXT_PUBLIC_`\nvariable is frozen at build time, so the same build could not serve dev and\nprod. One deploy-time setting on the API, no frontend rebuild.\n\nSince D5 closed on 8 Sep 2026 this reports two booleans instead of a Google\nclient ID. `email_sign_in` is always true and needs no third-party\ncredential: with no `RESEND_API_KEY` the code prints to the log instead of\nbeing emailed, so a fresh checkout can still sign in.","operationId":"getAuthConfig","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/AuthConfig"}}}}}}},"/api/v1/auth/email/start":{"post":{"tags":["auth"],"summary":"Email a 6-digit sign-in code","description":"Send a sign-in code to an address.\n\n**Always 202, whether or not an account exists.** There is nothing to leak:\nany verified address can sign in, and a first sign-in creates the account.\nThat also means this endpoint cannot be used to test whether someone has an\naccount here, which a \"no such user\" response would hand over for free.\n\n`delivered` reports whether it went to Resend or to the log. It is the\ndifference between \"check your inbox\" and \"check the backend log\", and on a\nfresh checkout with no `RESEND_API_KEY` it is the latter.","operationId":"authEmailStart","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/EmailStart"}}},"required":true},"responses":{"202":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/EmailStarted"}}}},"422":{"description":"`invalid_input` — not a valid address.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"error":{"code":"invalid_input","message":"a description of what went wrong"}}}}},"429":{"description":"`rate_limited` — too many codes for this address.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"error":{"code":"rate_limited","message":"a description of what went wrong"}}}}}}}},"/api/v1/auth/email/verify":{"post":{"tags":["auth"],"summary":"Exchange a sign-in code for a session","description":"Consume the code and issue a session, creating the account if needed.\n\nThe signup grant is written in the SAME transaction as the account, so a\ncrash between them cannot leave an account with no credits.","operationId":"authEmailVerify","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/EmailVerify"}}},"required":true},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Session"}}}},"422":{"description":"`invalid_input` — wrong, expired, used or exhausted.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"error":{"code":"invalid_input","message":"a description of what went wrong"}}}}}}}},"/api/v1/account":{"get":{"tags":["account"],"summary":"The signed-in account and its balance","operationId":"getAccount","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/AccountOut"}}}}},"security":[{"SessionAuth":[]}]}},"/api/v1/account/usage":{"get":{"tags":["account"],"summary":"Recent jobs and what each one cost","description":"One row per operation, newest first.\n\n`credits_used` is what was actually SETTLED, and is 0 for a refunded job.\nReporting the amount held would show a charge for work that was refunded,\nwhich is the line a customer screenshots.","operationId":"getAccountUsage","security":[{"SessionAuth":[]}],"parameters":[{"name":"limit","in":"query","required":false,"schema":{"type":"integer","maximum":200,"minimum":1,"default":50,"title":"Limit"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"array","items":{"$ref":"#/components/schemas/UsageRow"},"title":"Response Read Usage Api V1 Account Usage Get"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/api/v1/account/packs":{"get":{"tags":["account"],"summary":"Credit packs and where to buy them","description":"The pack catalogue, from the same table `/facts` and `llms.txt` use.\n\nOne source for the prices. Three hand-maintained copies would disagree\neventually, and the copy a customer sees before paying is the one that has\nto be right.","operationId":"getAccountPacks","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"items":{"$ref":"#/components/schemas/Pack"},"type":"array","title":"Response Read Packs Api V1 Account Packs Get"}}}}},"security":[{"SessionAuth":[]}]}},"/api/v1/operations":{"get":{"tags":["operations"],"summary":"Every operation a signed-in customer can run, and what it costs","description":"The run screen's source of truth.\n\n**Public, and it has to be.** A visitor deciding whether to buy needs to see\nwhat the product does and what each job costs before they have an account,\nwhich is the same reason `/pricing` is public. Nothing here is\naccount-specific: it is the catalog, not a quote.","operationId":"getOperations","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Catalog"}}}}}}},"/api/v1/keys":{"get":{"tags":["keys"],"summary":"List Keys","operationId":"getKeys","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/KeyList"}}}}},"security":[{"SessionAuth":[]}]},"post":{"tags":["keys"],"summary":"Create Key","operationId":"keys","responses":{"201":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/CreatedKey"}}}}},"security":[{"SessionAuth":[]}]}},"/api/v1/keys/{key_id}":{"delete":{"tags":["keys"],"summary":"Revoke Key","operationId":"deleteKeysByKeyId","security":[{"SessionAuth":[]}],"parameters":[{"name":"key_id","in":"path","required":true,"schema":{"type":"string","format":"uuid","title":"Key Id"}}],"responses":{"204":{"description":"Successful Response"},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/api/v1/render":{"post":{"tags":["render"],"summary":"Render markup to PDF","description":"Render markup to PDF, charging the caller's account.","operationId":"render","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/RenderRequest"}}},"required":true},"responses":{"200":{"description":"The rendered PDF.","content":{"application/pdf":{}}},"401":{"description":"`missing_api_key` or `invalid_api_key`.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"error":{"code":"missing_api_key","message":"a description of what went wrong"}}}}},"402":{"description":"`insufficient_credits`.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"error":{"code":"insufficient_credits","message":"a description of what went wrong"}}}}},"413":{"description":"`too_large`.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"error":{"code":"too_large","message":"a description of what went wrong"}}}}},"422":{"description":"`invalid_input`, `unsupported_format`, or `render_failed` when the engine rejected the markup. Do not retry these.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"error":{"code":"invalid_input","message":"a description of what went wrong"}}}}},"429":{"description":"`rate_limited`.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"error":{"code":"rate_limited","message":"a description of what went wrong"}}}}},"500":{"description":"`render_failed` — the engine itself failed. Retryable.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"error":{"code":"render_failed","message":"a description of what went wrong"}}}}}},"security":[{"ApiKeyAuth":[]},{"SessionAuth":[]}]}},"/api/v1/render/from-template":{"post":{"tags":["render"],"summary":"Render a stored template with data","description":"Fill a stored template's placeholders and render it.\n\nThe retention hook for product #1: a customer with a stored template posts a\nfew hundred bytes of data on a schedule instead of re-sending the markup\nevery time, which is the difference between a one-off job and a recurring\none. Same price as `/render` — the template is free, the render is not.","operationId":"renderFromTemplate","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/FromTemplateRequest"}}},"required":true},"responses":{"200":{"description":"The rendered PDF.","content":{"application/pdf":{}}},"401":{"description":"`missing_api_key` or `invalid_api_key`.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"error":{"code":"missing_api_key","message":"a description of what went wrong"}}}}},"402":{"description":"`insufficient_credits`.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"error":{"code":"insufficient_credits","message":"a description of what went wrong"}}}}},"413":{"description":"`too_large`.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"error":{"code":"too_large","message":"a description of what went wrong"}}}}},"422":{"description":"`invalid_input`, `unsupported_format`, or `render_failed` when the engine rejected the markup. Do not retry these.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"error":{"code":"invalid_input","message":"a description of what went wrong"}}}}},"429":{"description":"`rate_limited`.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"error":{"code":"rate_limited","message":"a description of what went wrong"}}}}},"500":{"description":"`render_failed` — the engine itself failed. Retryable.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"error":{"code":"render_failed","message":"a description of what went wrong"}}}}},"404":{"description":"`not_found` — no such template.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"error":{"code":"not_found","message":"a description of what went wrong"}}}}}},"security":[{"ApiKeyAuth":[]},{"SessionAuth":[]}]}},"/api/v1/render/batch":{"post":{"tags":["render"],"summary":"Render many documents, streaming results as Server-Sent Events","operationId":"renderBatch","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/BatchRequest"}}},"required":true},"responses":{"200":{"description":"metadata, item*, done","content":{"application/json":{"schema":{}},"text/event-stream":{}}},"401":{"description":"`missing_api_key` or `invalid_api_key`.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"error":{"code":"missing_api_key","message":"a description of what went wrong"}}}}},"422":{"description":"`invalid_input` — including too many items.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"error":{"code":"invalid_input","message":"a description of what went wrong"}}}}},"429":{"description":"`rate_limited`.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"error":{"code":"rate_limited","message":"a description of what went wrong"}}}}}},"security":[{"ApiKeyAuth":[]},{"SessionAuth":[]}]}},"/api/v1/pdf/{op}":{"post":{"tags":["pdf"],"summary":"Run a PDF operation","operationId":"pdfByOp","security":[{"ApiKeyAuth":[]},{"SessionAuth":[]}],"parameters":[{"name":"op","in":"path","required":true,"schema":{"$ref":"#/components/schemas/PdfOp"}}],"requestBody":{"required":true,"content":{"multipart/form-data":{"schema":{"$ref":"#/components/schemas/Body_run_pdf_op_api_v1_pdf__op__post"}}}},"responses":{"200":{"description":"The result. `split` returns a ZIP; everything else a PDF.","content":{"application/pdf":{},"application/zip":{}}},"401":{"description":"`missing_api_key` or `invalid_api_key`.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"error":{"code":"missing_api_key","message":"a description of what went wrong"}}}}},"402":{"description":"`insufficient_credits`.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"error":{"code":"insufficient_credits","message":"a description of what went wrong"}}}}},"413":{"description":"`too_large`.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"error":{"code":"too_large","message":"a description of what went wrong"}}}}},"422":{"description":"`invalid_input` — not a PDF, bad options, or encrypted.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"error":{"code":"invalid_input","message":"a description of what went wrong"}}}}},"429":{"description":"`rate_limited`.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"error":{"code":"rate_limited","message":"a description of what went wrong"}}}}},"500":{"description":"`render_failed`.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"error":{"code":"render_failed","message":"a description of what went wrong"}}}}}}}},"/api/v1/pdf/free/{op}":{"post":{"tags":["pdf"],"summary":"Compress or unlock one small PDF. Free, no API key.","description":"The free tool on `/compress` and `/unlock`.\n\n**Nothing is stored, and the password least of all.** The upload goes to a\ntmpfs workspace that is unlinked before the response completes. The password\nis never written to disk, never logged, and goes to qpdf **on stdin** rather\nthan in its argument vector — which used to be `--password=...`, readable in\n`/proc` by anything on the machine that can list processes. That was changed\nwhen this endpoint was written, because a keyless tool makes the exposure\neverybody's rather than one authenticated customer's. The page says all of\nthis, because \"type your PDF password into this website\" is a reasonable\nthing to hesitate over.\n\n**The same code as the paid endpoint**, through `_execute`. A second\nimplementation would be a second set of bugs and, worse, a free tool whose\noutput differs from what a customer gets after paying.","operationId":"pdfFreeByOp","parameters":[{"name":"op","in":"path","required":true,"schema":{"$ref":"#/components/schemas/PdfOp"}}],"requestBody":{"required":true,"content":{"multipart/form-data":{"schema":{"$ref":"#/components/schemas/Body_run_free_pdf_op_api_v1_pdf_free__op__post"}}}},"responses":{"200":{"description":"The PDF. `X-Original-Bytes` and `X-Result-Bytes` say what changed. The paid endpoint sends the same pair.","content":{"application/pdf":{}}},"413":{"description":"`too_large` — the free tier caps smaller than the API.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"error":{"code":"too_large","message":"a description of what went wrong"}}}}},"422":{"description":"`invalid_input` — not a PDF, not a free operation, or the wrong password.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"error":{"code":"invalid_input","message":"a description of what went wrong"}}}}},"429":{"description":"`rate_limited` — counted per IP address.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"error":{"code":"rate_limited","message":"a description of what went wrong"}}}}},"500":{"description":"`render_failed`.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"error":{"code":"render_failed","message":"a description of what went wrong"}}}}}}}},"/api/v1/pdf/{op}/batch":{"post":{"tags":["pdf"],"summary":"Run a PDF operation over many files, streaming results","description":"The same operation applied to each file independently, over SSE.\n\n**`merge` is refused here.** Batching means \"this op, to each file on its\nown\", and merge is many-to-one by definition — a batched merge is either\nevery file merged with nothing, or exactly what `POST /pdf/merge` already\ndoes. Refusing with that sentence is better than picking one and being wrong\nabout which the caller meant.\n\nOptions apply to every item. That is the point of a batch, and it is why\n`Options` is frozen: one instance is shared across concurrent items.","operationId":"pdfBatchByOp","security":[{"ApiKeyAuth":[]},{"SessionAuth":[]}],"parameters":[{"name":"op","in":"path","required":true,"schema":{"$ref":"#/components/schemas/PdfOp"}}],"requestBody":{"required":true,"content":{"multipart/form-data":{"schema":{"$ref":"#/components/schemas/Body_run_pdf_batch_api_v1_pdf__op__batch_post"}}}},"responses":{"200":{"description":"metadata, item*, done","content":{"application/json":{"schema":{}},"text/event-stream":{}}},"401":{"description":"`missing_api_key` or `invalid_api_key`.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"error":{"code":"missing_api_key","message":"a description of what went wrong"}}}}},"422":{"description":"`invalid_input` — including `merge`, which is not batchable.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"error":{"code":"invalid_input","message":"a description of what went wrong"}}}}},"429":{"description":"`rate_limited`.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"error":{"code":"rate_limited","message":"a description of what went wrong"}}}}}}}},"/api/v1/redact":{"post":{"tags":["redact"],"summary":"Remove text from a PDF so it cannot be recovered","operationId":"redact","requestBody":{"content":{"multipart/form-data":{"schema":{"$ref":"#/components/schemas/Body_run_redaction_api_v1_redact_post"}}},"required":true},"responses":{"200":{"description":"The redacted document. `X-Redaction-Summary` reports what was removed.","content":{"application/pdf":{}}},"401":{"description":"`missing_api_key` or `invalid_api_key`.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"error":{"code":"missing_api_key","message":"a description of what went wrong"}}}}},"402":{"description":"`insufficient_credits`.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"error":{"code":"insufficient_credits","message":"a description of what went wrong"}}}}},"413":{"description":"`too_large`.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"error":{"code":"too_large","message":"a description of what went wrong"}}}}},"422":{"description":"`invalid_input` — not a PDF, or nothing to redact.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"error":{"code":"invalid_input","message":"a description of what went wrong"}}}}},"429":{"description":"`rate_limited`.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"error":{"code":"rate_limited","message":"a description of what went wrong"}}}}},"500":{"description":"`render_failed` — including the case where the text survived the removal, in which case no document is returned.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"error":{"code":"render_failed","message":"a description of what went wrong"}}}}}},"security":[{"ApiKeyAuth":[]},{"SessionAuth":[]}]}},"/api/v1/metadata/inspect":{"post":{"tags":["metadata"],"summary":"Show what a file reveals about you. Free, no API key.","description":"Read a file's metadata without changing it, without a key, without a credit.\n\n**`POST`, not `GET`, despite the F-item's name.** It takes a file, and a GET\nhas nowhere to put one. The properties the item actually specifies — free,\nno key, rate-limited by IP — are all here.\n\nThis is the lead magnet for product #2: someone drops in a holiday\nphotograph, sees the coordinates of their house, and now understands what\nthe paid operation is for. It has to be free for that to happen, and free\nmeans it is the endpoint that gets abused, hence the lower size cap and the\nper-address limit.\n\n**No `CallerDep`.** Being outside the auth dependency is what makes it\nkeyless, and it is also why nothing here touches the ledger: there is no\naccount to charge, so the \"no credits consumed\" property is structural\nrather than something a code path has to remember.","operationId":"metadataInspect","requestBody":{"content":{"multipart/form-data":{"schema":{"$ref":"#/components/schemas/Body_inspect_metadata_api_v1_metadata_inspect_post"}}},"required":true},"responses":{"200":{"description":"What the file says, and which of it is sensitive.","content":{"application/json":{"schema":{"additionalProperties":true,"type":"object","title":"Response Inspect Metadata Api V1 Metadata Inspect Post"}}}},"413":{"description":"`too_large` — the free endpoint's limit is lower.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"error":{"code":"too_large","message":"a description of what went wrong"}}}}},"422":{"description":"`invalid_input` — not a PDF, JPEG or PNG.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"error":{"code":"invalid_input","message":"a description of what went wrong"}}}}},"429":{"description":"`rate_limited` — counted per IP address.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"error":{"code":"rate_limited","message":"a description of what went wrong"}}}}}}}},"/api/v1/metadata/strip":{"post":{"tags":["metadata"],"summary":"Remove metadata from a PDF, JPEG or PNG","operationId":"metadataStrip","requestBody":{"content":{"multipart/form-data":{"schema":{"$ref":"#/components/schemas/Body_strip_metadata_api_v1_metadata_strip_post"}}},"required":true},"responses":{"200":{"description":"The cleaned file, same type as the input. `X-Metadata-Removed` reports what came out.","content":{"application/pdf":{},"image/jpeg":{},"image/png":{}}},"401":{"description":"`missing_api_key` or `invalid_api_key`.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"error":{"code":"missing_api_key","message":"a description of what went wrong"}}}}},"402":{"description":"`insufficient_credits`.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"error":{"code":"insufficient_credits","message":"a description of what went wrong"}}}}},"413":{"description":"`too_large`.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"error":{"code":"too_large","message":"a description of what went wrong"}}}}},"422":{"description":"`invalid_input` — not a PDF, JPEG or PNG.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"error":{"code":"invalid_input","message":"a description of what went wrong"}}}}},"429":{"description":"`rate_limited`.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"error":{"code":"rate_limited","message":"a description of what went wrong"}}}}},"500":{"description":"`render_failed`.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"error":{"code":"render_failed","message":"a description of what went wrong"}}}}}},"security":[{"ApiKeyAuth":[]},{"SessionAuth":[]}]}},"/api/v1/parse":{"post":{"tags":["parse"],"summary":"Parse a document to markdown","operationId":"parse","requestBody":{"content":{"multipart/form-data":{"schema":{"$ref":"#/components/schemas/Body_parse_document_api_v1_parse_post"}}},"required":true},"responses":{"200":{"description":"Markdown, and chunks when asked for.","content":{"application/json":{"schema":{"additionalProperties":true,"type":"object","title":"Response Parse Document Api V1 Parse Post"}}}},"401":{"description":"`missing_api_key` or `invalid_api_key`.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"error":{"code":"missing_api_key","message":"a description of what went wrong"}}}}},"402":{"description":"`insufficient_credits`.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"error":{"code":"insufficient_credits","message":"a description of what went wrong"}}}}},"413":{"description":"`too_large`.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"error":{"code":"too_large","message":"a description of what went wrong"}}}}},"422":{"description":"`invalid_input` — unsupported format or bad options.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"error":{"code":"invalid_input","message":"a description of what went wrong"}}}}},"429":{"description":"`rate_limited`.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"error":{"code":"rate_limited","message":"a description of what went wrong"}}}}},"500":{"description":"`render_failed`.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"error":{"code":"render_failed","message":"a description of what went wrong"}}}}}},"security":[{"ApiKeyAuth":[]},{"SessionAuth":[]}]}},"/api/v1/parse/batch":{"post":{"tags":["parse"],"summary":"Parse many documents, streaming results","description":"The same parse applied to each document, over SSE.\n\n**Each item is priced on its own**, unlike every other batch here: a mixed\nupload of typed and scanned documents has no single per-item cost, and\ncharging the OCR rate across the batch because one item was a scan is the\novercharge F68 exists to prevent. `per_item_credits` is therefore the\n*cheapest* possible item, and the real figure rides in each event.","operationId":"parseBatch","requestBody":{"content":{"multipart/form-data":{"schema":{"$ref":"#/components/schemas/Body_parse_batch_api_v1_parse_batch_post"}}},"required":true},"responses":{"200":{"description":"metadata, item*, done","content":{"application/json":{"schema":{}},"text/event-stream":{}}},"401":{"description":"`missing_api_key` or `invalid_api_key`.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"error":{"code":"missing_api_key","message":"a description of what went wrong"}}}}},"422":{"description":"`invalid_input`.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"error":{"code":"invalid_input","message":"a description of what went wrong"}}}}},"429":{"description":"`rate_limited`.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"error":{"code":"rate_limited","message":"a description of what went wrong"}}}}}},"security":[{"ApiKeyAuth":[]},{"SessionAuth":[]}]}},"/api/v1/convert/formats":{"get":{"tags":["convert"],"summary":"Every conversion this API can actually do. Free, no API key.","description":"The capability list, probed from the installed binaries.\n\n**Free and unauthenticated**, like `/metadata/inspect`: a customer deciding\nwhether to buy needs to know whether their pair is supported, and putting\nthat behind a key means finding out by spending a credit on a 422.","operationId":"getConvertFormats","responses":{"200":{"description":"The probed pair list, not a declared one.","content":{"application/json":{"schema":{"additionalProperties":true,"type":"object","title":"Response List Formats Api V1 Convert Formats Get"}}}}}}},"/api/v1/convert/free":{"post":{"tags":["convert"],"summary":"Convert one small document. Free, no API key.","description":"The working tool that sits on every generated page (F86).\n\n**This endpoint is what makes the generated page set defensible.** The rule\nkept from the superseded scaled-content-abuse restriction is that every page\ncarries a working tool — that is the property distinguishing iLovePDF's\nsurviving pages from a doorway set. A conversion page cannot run pandoc,\nLibreOffice or calibre in a browser, and shipping 64 pages whose \"tool\" is a\nlink to the docs would be exactly the pattern the policy describes.\n\nSame shape as `/metadata/inspect`, and for the same reasons: no `CallerDep`,\nso keyless is structural rather than remembered; nothing touches the ledger,\nso \"no credits consumed\" cannot drift; lower size cap and a per-address rate\nlimit, because free is what gets abused.\n\n`PRD.md` §3.1's free-vs-credits split is the boundary: one file per run, a\nsmaller cap, web UI only. Batch, large documents and automation are the API.","operationId":"convertFree","requestBody":{"content":{"multipart/form-data":{"schema":{"$ref":"#/components/schemas/Body_convert_free_api_v1_convert_free_post"}}},"required":true},"responses":{"200":{"description":"The converted file."},"413":{"description":"`too_large` — the free endpoint's limit is lower.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"error":{"code":"too_large","message":"a description of what went wrong"}}}}},"422":{"description":"`invalid_input` — unsupported pair, or a mislabelled file.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"error":{"code":"invalid_input","message":"a description of what went wrong"}}}}},"429":{"description":"`rate_limited` — counted per IP address.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"error":{"code":"rate_limited","message":"a description of what went wrong"}}}}}}}},"/api/v1/convert":{"post":{"tags":["convert"],"summary":"Convert a document from one format to another","operationId":"convert","requestBody":{"content":{"multipart/form-data":{"schema":{"$ref":"#/components/schemas/Body_convert_document_api_v1_convert_post"}}},"required":true},"responses":{"200":{"description":"The converted file. `X-Convert-Engine` says who did it."},"401":{"description":"`missing_api_key` or `invalid_api_key`.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"error":{"code":"missing_api_key","message":"a description of what went wrong"}}}}},"402":{"description":"`insufficient_credits`.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"error":{"code":"insufficient_credits","message":"a description of what went wrong"}}}}},"413":{"description":"`too_large`.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"error":{"code":"too_large","message":"a description of what went wrong"}}}}},"422":{"description":"`invalid_input` — unsupported pair, or a mislabelled file. Also `render_failed` when a file could not be read at all, including an unreadable `reference_doc`. Do not retry these.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"error":{"code":"invalid_input","message":"a description of what went wrong"}}}}},"429":{"description":"`rate_limited`.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"error":{"code":"rate_limited","message":"a description of what went wrong"}}}}},"500":{"description":"`render_failed` — the engine could try and did not succeed. Retryable.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"error":{"code":"render_failed","message":"a description of what went wrong"}}}}}},"security":[{"ApiKeyAuth":[]},{"SessionAuth":[]}]}},"/api/v1/tables":{"post":{"tags":["tables"],"summary":"Extract tables from a PDF as CSV, XLSX or JSON","operationId":"tables","requestBody":{"content":{"multipart/form-data":{"schema":{"$ref":"#/components/schemas/Body_extract_tables_api_v1_tables_post"}}},"required":true},"responses":{"200":{"description":"The tables. `X-Tables-Found` and `X-Table-Pages` say what was charged for.","content":{"text/csv":{},"application/zip":{},"application/vnd.openxmlformats-officedocument.spreadsheetml.sheet":{},"application/json":{}}},"401":{"description":"`missing_api_key` or `invalid_api_key`.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"error":{"code":"missing_api_key","message":"a description of what went wrong"}}}}},"402":{"description":"`insufficient_credits`.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"error":{"code":"insufficient_credits","message":"a description of what went wrong"}}}}},"413":{"description":"`too_large`.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"error":{"code":"too_large","message":"a description of what went wrong"}}}}},"422":{"description":"`invalid_input` — not a PDF, or a page range outside the document.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"error":{"code":"invalid_input","message":"a description of what went wrong"}}}}},"429":{"description":"`rate_limited`.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"error":{"code":"rate_limited","message":"a description of what went wrong"}}}}},"500":{"description":"`render_failed`.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"error":{"code":"render_failed","message":"a description of what went wrong"}}}}}},"security":[{"ApiKeyAuth":[]},{"SessionAuth":[]}]}},"/api/v1/tables/detect":{"post":{"tags":["tables"],"summary":"Which pages have tables, and what it would cost. Free, no API key.","description":"The price, before the purchase.\n\n**Free and keyless on purpose.** This is priced per page, so the cost of a\nrequest is not knowable from the file's name or size — and a customer who\ncan only find out by being charged will assume the worst. It is also the\ntool that sits on F93's landing pages: someone drops a statement in and sees\n\"6 of 40 pages have tables, 18 credits\" before deciding anything.\n\nDetection only. No extraction, so nothing here is worth stealing as a free\ntier: the answer is a page list, not a spreadsheet.","operationId":"tablesDetect","requestBody":{"content":{"multipart/form-data":{"schema":{"$ref":"#/components/schemas/Body_detect_tables_api_v1_tables_detect_post"}}},"required":true},"responses":{"200":{"description":"Page numbers, table counts and the price.","content":{"application/json":{"schema":{"additionalProperties":true,"type":"object","title":"Response Detect Tables Api V1 Tables Detect Post"}}}},"413":{"description":"`too_large` — the free endpoint's limit is lower.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"error":{"code":"too_large","message":"a description of what went wrong"}}}}},"422":{"description":"`invalid_input` — not a PDF.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"error":{"code":"invalid_input","message":"a description of what went wrong"}}}}},"429":{"description":"`rate_limited` — counted per IP address.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"error":{"code":"rate_limited","message":"a description of what went wrong"}}}}}}}},"/api/v1/spreadsheet/clean":{"post":{"tags":["spreadsheet"],"summary":"Clean a messy spreadsheet: headers, types, merged cells, audit log","operationId":"spreadsheetClean","requestBody":{"content":{"multipart/form-data":{"schema":{"$ref":"#/components/schemas/Body_clean_spreadsheet_api_v1_spreadsheet_clean_post"}}},"required":true},"responses":{"200":{"description":"The cleaned data. `X-Rows`, `X-Changed-Cells` and `X-Credits-Used` say what was charged for and why.","content":{"text/csv":{},"application/json":{},"application/vnd.openxmlformats-officedocument.spreadsheetml.sheet":{}}},"401":{"description":"`missing_api_key` or `invalid_api_key`.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"error":{"code":"missing_api_key","message":"a description of what went wrong"}}}}},"402":{"description":"`insufficient_credits`.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"error":{"code":"insufficient_credits","message":"a description of what went wrong"}}}}},"413":{"description":"`too_large`.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"error":{"code":"too_large","message":"a description of what went wrong"}}}}},"422":{"description":"`invalid_input` — not a readable spreadsheet, or no data rows. `too_many_pages` — past the row cap for one request.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"error":{"code":"invalid_input","message":"a description of what went wrong"}}}}},"429":{"description":"`rate_limited`.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"error":{"code":"rate_limited","message":"a description of what went wrong"}}}}}},"security":[{"ApiKeyAuth":[]},{"SessionAuth":[]}]}},"/api/v1/spreadsheet/preview":{"post":{"tags":["spreadsheet"],"summary":"What would change, and what it would cost. Free, no API key.","description":"The changes, before the purchase. **No cleaned data comes back.**\n\nThat is what keeps this from being a free tier of the product: the caller\nlearns which columns would be reinterpreted, sees a few examples per kind,\nand gets the price. To have the file they have to buy it.","operationId":"spreadsheetPreview","requestBody":{"content":{"multipart/form-data":{"schema":{"$ref":"#/components/schemas/Body_preview_spreadsheet_api_v1_spreadsheet_preview_post"}}},"required":true},"responses":{"200":{"description":"The diff, the detected structure and the price.","content":{"application/json":{"schema":{"additionalProperties":true,"type":"object","title":"Response Preview Spreadsheet Api V1 Spreadsheet Preview Post"}}}},"413":{"description":"`too_large` — the free endpoint's limit is lower.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"error":{"code":"too_large","message":"a description of what went wrong"}}}}},"422":{"description":"`invalid_input`, or `too_many_pages` past the row cap.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"error":{"code":"invalid_input","message":"a description of what went wrong"}}}}},"429":{"description":"`rate_limited` — counted per IP address.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"error":{"code":"rate_limited","message":"a description of what went wrong"}}}}}}}},"/api/v1/extract":{"post":{"tags":["extract"],"summary":"Read a web page as clean markdown, text, HTML, PDF or PNG","operationId":"extract","requestBody":{"content":{"application/x-www-form-urlencoded":{"schema":{"$ref":"#/components/schemas/Body_extract_page_api_v1_extract_post"}}},"required":true},"responses":{"200":{"description":"The article. `X-Extract-Engine`, `X-Blocked-Requests` and `X-Extract-Title` say how it was produced.","content":{"text/markdown":{},"text/plain":{},"text/html":{},"application/pdf":{},"image/png":{}}},"401":{"description":"`missing_api_key` or `invalid_api_key`.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"error":{"code":"missing_api_key","message":"a description of what went wrong"}}}}},"402":{"description":"`insufficient_credits`.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"error":{"code":"insufficient_credits","message":"a description of what went wrong"}}}}},"422":{"description":"`invalid_input` — not an http(s) URL, or a private address.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"error":{"code":"invalid_input","message":"a description of what went wrong"}}}}},"429":{"description":"`rate_limited`.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"error":{"code":"rate_limited","message":"a description of what went wrong"}}}}},"500":{"description":"`render_failed` — the page did not load.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"error":{"code":"render_failed","message":"a description of what went wrong"}}}}}},"security":[{"ApiKeyAuth":[]},{"SessionAuth":[]}]}},"/api/v1/extract/preview":{"post":{"tags":["extract"],"summary":"Read one page as markdown. Free, no API key, truncated.","description":"The free reader on F117's landing pages.\n\n**Truncated on purpose, and the truncation is stated.** #6 is a flat credit\nper page, so unlike #8's scanner there is no price to quote — the honest free\ntool is a real answer with a visible end, not a fake one. Someone can see the\nextraction quality on their own URL, which is the only question a landing\npage for this product has to answer, and reading a whole article costs a\ncredit like it does for everyone.","operationId":"extractPreview","requestBody":{"content":{"application/x-www-form-urlencoded":{"schema":{"$ref":"#/components/schemas/Body_preview_page_api_v1_extract_preview_post"}}},"required":true},"responses":{"200":{"description":"The first part of the article, and what was blocked.","content":{"application/json":{"schema":{"additionalProperties":true,"type":"object","title":"Response Preview Page Api V1 Extract Preview Post"}}}},"422":{"description":"`invalid_input`.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"error":{"code":"invalid_input","message":"a description of what went wrong"}}}}},"429":{"description":"`rate_limited` — counted per IP address.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"error":{"code":"rate_limited","message":"a description of what went wrong"}}}}}}}},"/api/v1/extract/preview/pdf":{"post":{"tags":["extract"],"summary":"The page as a PDF, first 3 pages. Free, no API key.","description":"The free PDF on `/webpage-to-pdf/` (UI43).\n\n**Why this is not the same page load as `/preview`.** The markdown preview\npasses `text_only`, which blocks images and CSS: cheaper, and it produces a\nPDF of a page with no pictures and no layout, which is worse than no PDF at\nall. So this asks for a `png`-grade load and takes the PDF out of it.\n\n**Trimmed with pdfcpu, in tmpfs, unlinked on the way out.** The alternative,\nstopping Chromium early, does not exist: a PDF comes out of one `printToPDF`\nwhole. A14's promise holds either way — nothing is written outside the\nworkspace and nothing survives the response.\n\nThe same per-address budget as every other keyless endpoint that does work.","operationId":"extractPreviewPdf","requestBody":{"content":{"application/x-www-form-urlencoded":{"schema":{"$ref":"#/components/schemas/Body_preview_pdf_api_v1_extract_preview_pdf_post"}}},"required":true},"responses":{"200":{"description":"The page rendered to PDF, trimmed to 3 pages. `X-Pdf-Pages` is what was returned and `X-Pdf-Pages-Total` what the page actually ran to.","content":{"application/pdf":{}}},"422":{"description":"`invalid_input` — not an http(s) URL, or a private address.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"error":{"code":"invalid_input","message":"a description of what went wrong"}}}}},"429":{"description":"`rate_limited` — counted per IP address.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"error":{"code":"rate_limited","message":"a description of what went wrong"}}}}},"500":{"description":"`render_failed` — the page did not load.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"error":{"code":"render_failed","message":"a description of what went wrong"}}}}}}}},"/api/v1/youtube":{"post":{"tags":["youtube"],"summary":"Get a YouTube video's transcript as txt, srt, vtt or json","operationId":"youtube","requestBody":{"content":{"application/x-www-form-urlencoded":{"schema":{"$ref":"#/components/schemas/Body_youtube_transcript_api_v1_youtube_post"}}},"required":true},"responses":{"200":{"description":"The transcript. `X-Transcript-Source` says whether it came from the author's captions or YouTube's automatic ones.","content":{"text/plain":{},"application/x-subrip":{},"text/vtt":{},"application/json":{}}},"401":{"description":"`missing_api_key` or `invalid_api_key`.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"error":{"code":"missing_api_key","message":"a description of what went wrong"}}}}},"402":{"description":"`insufficient_credits`.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"error":{"code":"insufficient_credits","message":"a description of what went wrong"}}}}},"422":{"description":"`invalid_input` — not a YouTube URL, a live stream, over the duration limit, no captions at all, or no track in that language.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"error":{"code":"invalid_input","message":"a description of what went wrong"}}}}},"429":{"description":"`rate_limited`.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"error":{"code":"rate_limited","message":"a description of what went wrong"}}}}},"500":{"description":"`internal_error` — the extractor could not read the page.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"error":{"code":"internal_error","message":"a description of what went wrong"}}}}}},"security":[{"ApiKeyAuth":[]},{"SessionAuth":[]}]}},"/api/v1/youtube/preview":{"post":{"tags":["youtube"],"summary":"Get a transcript. Free, no API key, truncated.","description":"The free tool on F122's landing page.\n\n**F122 flagged the free-tool pattern as the one place it did not transfer**,\nbecause a keyless *video* download is an open bandwidth proxy that costs real\nmoney per click. With video gone that objection goes with it: this spends one\nmetadata call and one small text fetch, which is the same shape as every\nother free tool on the site.\n\n**Truncated, and the truncation is stated.** Long enough to prove the\ntranscript is real and the quality is what it is; short enough that the paid\nendpoint still has a job.","operationId":"youtubePreview","requestBody":{"content":{"application/x-www-form-urlencoded":{"schema":{"$ref":"#/components/schemas/Body_preview_transcript_api_v1_youtube_preview_post"}}},"required":true},"responses":{"200":{"description":"The first part of the transcript, and which track it came from.","content":{"application/json":{"schema":{"additionalProperties":true,"type":"object","title":"Response Preview Transcript Api V1 Youtube Preview Post"}}}},"422":{"description":"`invalid_input`.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"error":{"code":"invalid_input","message":"a description of what went wrong"}}}}},"429":{"description":"`rate_limited` — counted per IP address.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"error":{"code":"rate_limited","message":"a description of what went wrong"}}}}}}}},"/api/v1/capture":{"post":{"tags":["capture"],"summary":"Capture a URL as evidence","operationId":"capture","security":[{"ApiKeyAuth":[]},{"SessionAuth":[]}],"requestBody":{"required":true,"content":{"application/x-www-form-urlencoded":{"schema":{"$ref":"#/components/schemas/Body_create_capture_api_v1_capture_post"}}}},"responses":{"200":{"description":"The manifest, hashes, and whether it was timestamped.","content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"title":"Response Create Capture Api V1 Capture Post"}}}},"401":{"description":"`missing_api_key` or `invalid_api_key`.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"error":{"code":"missing_api_key","message":"a description of what went wrong"}}}}},"402":{"description":"`insufficient_credits`.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"error":{"code":"insufficient_credits","message":"a description of what went wrong"}}}}},"422":{"description":"`invalid_input` — a URL that must not be fetched.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"error":{"code":"invalid_input","message":"a description of what went wrong"}}}}},"429":{"description":"`rate_limited`.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"error":{"code":"rate_limited","message":"a description of what went wrong"}}}}},"500":{"description":"`render_failed` — the page could not be loaded.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"error":{"code":"render_failed","message":"a description of what went wrong"}}}}}}},"get":{"tags":["capture"],"summary":"List this account's captures","operationId":"getCapture","security":[{"ApiKeyAuth":[]},{"SessionAuth":[]}],"parameters":[{"name":"limit","in":"query","required":false,"schema":{"type":"integer","default":50,"title":"Limit"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"title":"Response List Captures Api V1 Capture Get"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/api/v1/capture/{capture_id}/verify":{"get":{"tags":["capture"],"summary":"Check a capture's hashes. Public, no API key.","description":"The trust artefact, and the reason the rest of this product exists.\n\n**Unauthenticated on purpose.** The person who needs to check a capture is\nthe one being shown it — an opposing solicitor, a regulator, a judge — and\nthey have no account here and never will. A verification page that requires\nthe owner's credentials verifies nothing to anybody who matters.\n\nRate-limited by IP like every other keyless endpoint, and it reads only:\nthere is nothing here to spend and nothing to change. That is why it counts\nagainst the READ budget rather than the free-work one — a solicitor\nre-checking a manifest must not be able to exhaust a shared address's\nallowance for the marketing site's tools.","operationId":"getCaptureVerifyByCaptureId","parameters":[{"name":"capture_id","in":"path","required":true,"schema":{"type":"string","description":"The capture id from the manifest.","title":"Capture Id"},"description":"The capture id from the manifest."}],"responses":{"200":{"description":"The manifest and a per-artefact verdict.","content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"title":"Response Verify Capture Api V1 Capture  Capture Id  Verify Get"}}}},"404":{"description":"`not_found` — no such capture, or it was not retained.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"error":{"code":"not_found","message":"a description of what went wrong"}}}}},"429":{"description":"`rate_limited` — counted per IP address.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"error":{"code":"rate_limited","message":"a description of what went wrong"}}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/api/v1/capture/{capture_id}":{"delete":{"tags":["capture"],"summary":"Delete a capture and its stored files. Yours only.","description":"**The other half of \"retained until you delete them\".** `BUGS.md` UI52.\n\nThe console said it, every #4 footer said it, and `/legal/retention/` said it\nin its exception list, and until 7 Oct 2026 the only way to stop storing a\ncapture was to ask a human. On the one product here that keeps anything, that\nis the sentence that has to be a mechanism.\n\nAuthenticated and scoped to the caller, unlike `verify` and the artefact\ndownload: reading a capture you were given a reference to is the product,\ndestroying one is not. The verify link stays public and starts returning 404\nthe moment this succeeds, which is the correct answer to \"has this been\ndeleted\".","operationId":"deleteCaptureByCaptureId","security":[{"ApiKeyAuth":[]},{"SessionAuth":[]}],"parameters":[{"name":"capture_id","in":"path","required":true,"schema":{"type":"string","description":"The capture to erase.","title":"Capture Id"},"description":"The capture to erase."}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"204":{"description":"Gone. The bytes are removed from the vault too."},"404":{"description":"`not_found` — not yours, or already gone.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"error":{"code":"not_found","message":"a description of what went wrong"}}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/api/v1/capture/{capture_id}/artefact/{name}":{"get":{"tags":["capture"],"summary":"Download one stored artefact. Public, no API key.","description":"**The files the product is sold as.** `BUGS.md` UI45.\n\nEverything else about a capture worked: four artefacts written, hashed,\nRFC-3161 timestamped, and verifiable by a stranger with no account. There was\nno way to fetch one. A $6 buyer received an email with a verify link, and the\nsigned-in owner of a retained capture saw a `Verify` link and nothing else,\nwhile their 4,862 KB sat in R2.\n\n**Keyless, for the same reason `verify` is.** The point of a capture is that\nthe other side can check it, and a screenshot they cannot open is not\nevidence to them. The id is an unguessable UUID and it is the capability — the\nsame model as the verify link that is already public, and the same one the\nemailed link relies on. Making this one authenticated would mean the holder\nof a shared reference can verify hashes but not see the page they describe.\n\nOnly artefacts named in the signed manifest are served, so this cannot be\nwalked into a directory listing, and `..` or any other name simply is not in\nthe manifest and 404s.","operationId":"getCaptureArtefactByCaptureIdAndName","parameters":[{"name":"capture_id","in":"path","required":true,"schema":{"type":"string","description":"The capture id from the manifest.","title":"Capture Id"},"description":"The capture id from the manifest."},{"name":"name","in":"path","required":true,"schema":{"type":"string","description":"An artefact name from the manifest.","title":"Name"},"description":"An artefact name from the manifest."}],"responses":{"200":{"description":"The bytes, with the media type the manifest records."},"404":{"description":"`not_found` — no such capture or artefact, or it was not retained.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"error":{"code":"not_found","message":"a description of what went wrong"}}}}},"429":{"description":"`rate_limited` — counted per IP address.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"error":{"code":"rate_limited","message":"a description of what went wrong"}}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/api/v1/capture/schedule":{"get":{"tags":["capture"],"summary":"List this account's capture schedules","operationId":"getCaptureSchedule","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"additionalProperties":true,"type":"object","title":"Response List Schedules Api V1 Capture Schedule Get"}}}}},"security":[{"ApiKeyAuth":[]},{"SessionAuth":[]}]},"post":{"tags":["capture"],"summary":"Capture a URL repeatedly","description":"Recurring capture. **No credits move here** — each RUN is billed.\n\nCharging at schedule time would be charging for work not yet done, and a\nschedule that is paused or deleted before its first run would have taken\nmoney for nothing.","operationId":"captureSchedule","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ScheduleIn"}}},"required":true},"responses":{"200":{"description":"The schedule.","content":{"application/json":{"schema":{"additionalProperties":true,"type":"object","title":"Response Create Schedule Api V1 Capture Schedule Post"}}}},"401":{"description":"`missing_api_key` or `invalid_api_key`.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"error":{"code":"missing_api_key","message":"a description of what went wrong"}}}}},"422":{"description":"`invalid_input`.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"error":{"code":"invalid_input","message":"a description of what went wrong"}}}}}},"security":[{"ApiKeyAuth":[]},{"SessionAuth":[]}]}},"/api/v1/capture/schedule/{schedule_id}":{"patch":{"tags":["capture"],"summary":"Pause, resume or re-time a schedule","operationId":"updateCaptureScheduleByScheduleId","security":[{"ApiKeyAuth":[]},{"SessionAuth":[]}],"parameters":[{"name":"schedule_id","in":"path","required":true,"schema":{"type":"string","title":"Schedule Id"}}],"requestBody":{"content":{"application/x-www-form-urlencoded":{"schema":{"$ref":"#/components/schemas/Body_update_schedule_api_v1_capture_schedule__schedule_id__patch"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"title":"Response Update Schedule Api V1 Capture Schedule  Schedule Id  Patch"}}}},"404":{"description":"`not_found` — not yours, or gone.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"error":{"code":"not_found","message":"a description of what went wrong"}}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}},"delete":{"tags":["capture"],"summary":"Delete a schedule. Its captures are kept.","description":"**The captures survive.** `schedule_id` is nullable and does not cascade.\n\nDeleting a schedule is an operational act — \"stop doing this\". Deleting the\nevidence it collected is a different act, and it must not happen as a side\neffect of the first one.","operationId":"deleteCaptureScheduleByScheduleId","security":[{"ApiKeyAuth":[]},{"SessionAuth":[]}],"parameters":[{"name":"schedule_id","in":"path","required":true,"schema":{"type":"string","title":"Schedule Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"404":{"description":"`not_found` — not yours, or already gone.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"error":{"code":"not_found","message":"a description of what went wrong"}}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/api/v1/capture/job":{"post":{"tags":["capture"],"summary":"Record a URL and get a payment link. No API key, no account.","description":"Check the URL, record the job, and hand back a payment link.\n\n**Keyless and rate-limited by IP**, like every other endpoint here with no\naccount behind it. Nothing touches the ledger.\n\n**The URL is validated before payment**, through the same `netguard` the\npaid endpoint uses — private addresses, loopback and the rest are refused\nhere rather than after the money has moved. A customer who pays and then\nlearns the address was never fetchable has been taken money for nothing.","operationId":"captureJob","requestBody":{"content":{"application/x-www-form-urlencoded":{"schema":{"$ref":"#/components/schemas/Body_create_job_api_v1_capture_job_post"}}},"required":true},"responses":{"200":{"description":"The job id, the price, and where to pay.","content":{"application/json":{"schema":{"additionalProperties":true,"type":"object","title":"Response Create Job Api V1 Capture Job Post"}}}},"422":{"description":"`invalid_input` — a URL that must not be fetched.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"error":{"code":"invalid_input","message":"a description of what went wrong"}}}}},"429":{"description":"`rate_limited` — counted per IP address.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"error":{"code":"rate_limited","message":"a description of what went wrong"}}}}},"503":{"description":"`internal_error` — no payment link is configured yet.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"error":{"code":"internal_error","message":"a description of what went wrong"}}}}}}}},"/api/v1/capture/job/{job_id}":{"get":{"tags":["capture"],"summary":"How a per-job capture is getting on. No API key.","description":"Poll after paying. **The id is the credential.**\n\nA random UUID that only the person who submitted the URL has been shown is\nthe whole of the authorisation here, which is the price of having no\naccounts. It grants nothing except this one job.\n\nCounted against the READ budget, not the free-work one: this is polled every\nfew seconds by a customer who has already paid, and charging that to the\nsame allowance as a submission locked the whole address out (BUGS.md UI28).","operationId":"getCaptureJobByJobId","parameters":[{"name":"job_id","in":"path","required":true,"schema":{"type":"string","title":"Job Id"}}],"responses":{"200":{"description":"Status, and the verification link once it is ready.","content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"title":"Response Job Status Api V1 Capture Job  Job Id  Get"}}}},"404":{"description":"`not_found`.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"error":{"code":"not_found","message":"a description of what went wrong"}}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/api/v1/archive/job":{"post":{"tags":["archive"],"summary":"Upload an export and get a payment link. No API key, no account.","description":"Upload, parse, hold, and hand back a payment link.\n\n**Keyless and rate-limited by IP**, like every other endpoint here that has\nno account behind it. Nothing touches the ledger, so \"no credits consumed\"\nis structural rather than something a code path has to remember.\n\n**The file is parsed before payment.** A customer who pays and then learns\ntheir export is unreadable has been taken money for nothing, and the refund\ncosts more than the sale was worth. The response says how many messages were\nfound and what it will cost, so they know what they are buying.\n\n**The price is flat and does not depend on the message count** just returned.\nThat is worth being explicit about here, because the two numbers sit next to\neach other in the response and a per-message reading of them would be wrong:\na five-year group chat and a fifty-message thread cost the same.","operationId":"archiveJob","requestBody":{"content":{"multipart/form-data":{"schema":{"$ref":"#/components/schemas/Body_create_job_api_v1_archive_job_post"}}},"required":true},"responses":{"200":{"description":"The job id, what was found in the file, and where to pay.","content":{"application/json":{"schema":{"additionalProperties":true,"type":"object","title":"Response Create Job Api V1 Archive Job Post"}}}},"413":{"description":"`too_large`.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"error":{"code":"too_large","message":"a description of what went wrong"}}}}},"422":{"description":"`invalid_input` — not an export this can read.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"error":{"code":"invalid_input","message":"a description of what went wrong"}}}}},"429":{"description":"`rate_limited` — counted per IP address.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"error":{"code":"rate_limited","message":"a description of what went wrong"}}}}},"503":{"description":"`internal_error` — no payment link is configured yet.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"error":{"code":"internal_error","message":"a description of what went wrong"}}}}}}}},"/api/v1/archive/job/{job_id}":{"get":{"tags":["archive"],"summary":"How a per-job archive is getting on. No API key.","description":"Poll after paying. **The id is the credential.**\n\nA random UUID that only the person who uploaded the file has been shown is\nthe whole of the authorisation here, which is the price of having no\naccounts. It is not guessable, it is not enumerable, and it grants nothing\nexcept this one archive.\n\nCounted against the READ budget, not the free-work one: this is polled every\nfew seconds by a customer who has already paid, and charging that to the\nsame allowance as an upload locked the whole address out (BUGS.md UI28).","operationId":"getArchiveJobByJobId","parameters":[{"name":"job_id","in":"path","required":true,"schema":{"type":"string","title":"Job Id"}}],"responses":{"200":{"description":"Status, and a download link once it is ready.","content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"title":"Response Job Status Api V1 Archive Job  Job Id  Get"}}}},"404":{"description":"`not_found`.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"error":{"code":"not_found","message":"a description of what went wrong"}}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/api/v1/archive/job/{job_id}/download":{"get":{"tags":["archive"],"summary":"The finished archive. No API key.","description":"The result, for the person holding the id.\n\n**Also emailed**, because a consumer who closes the tab has no other way\nback. The download exists so the common case — pay, come back, take the file\n— does not depend on an email arriving.","operationId":"getArchiveJobDownloadByJobId","parameters":[{"name":"job_id","in":"path","required":true,"schema":{"type":"string","title":"Job Id"}}],"responses":{"200":{"description":"The archive.","content":{"application/json":{"schema":{}}}},"402":{"description":"`insufficient_credits` — not paid for yet.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"error":{"code":"insufficient_credits","message":"a description of what went wrong"}}}}},"404":{"description":"`not_found`.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"error":{"code":"not_found","message":"a description of what went wrong"}}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/api/v1/archive":{"post":{"tags":["archive"],"summary":"Turn a chat or mail export into a readable archive","operationId":"archive","requestBody":{"content":{"multipart/form-data":{"schema":{"$ref":"#/components/schemas/Body_create_archive_api_v1_archive_post"}}},"required":true},"responses":{"200":{"description":"The archive. HTML is one self-contained file.","content":{"text/html":{},"application/pdf":{},"application/json":{}}},"401":{"description":"`missing_api_key` or `invalid_api_key`.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"error":{"code":"missing_api_key","message":"a description of what went wrong"}}}}},"402":{"description":"`insufficient_credits`.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"error":{"code":"insufficient_credits","message":"a description of what went wrong"}}}}},"413":{"description":"`too_large`.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"error":{"code":"too_large","message":"a description of what went wrong"}}}}},"422":{"description":"`invalid_input` — not an export this can read.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"error":{"code":"invalid_input","message":"a description of what went wrong"}}}}},"429":{"description":"`rate_limited`.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"error":{"code":"rate_limited","message":"a description of what went wrong"}}}}}},"security":[{"ApiKeyAuth":[]},{"SessionAuth":[]}]}},"/api/v1/stripe/webhook":{"post":{"tags":["billing"],"summary":"Stripe webhook. Signature-verified; the only path that adds credits.","operationId":"stripeWebhook","responses":{"200":{"description":"Acknowledged. `credited` says whether anything moved.","content":{"application/json":{"schema":{"additionalProperties":true,"type":"object","title":"Response Stripe Webhook Endpoint Api V1 Stripe Webhook Post"}}}},"422":{"description":"`invalid_input` — the signature did not verify.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"error":{"code":"invalid_input","message":"a description of what went wrong"}}}}}}}},"/api/v1/templates":{"get":{"tags":["templates"],"summary":"List this account's templates","operationId":"getTemplates","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"items":{"$ref":"#/components/schemas/TemplateOut"},"type":"array","title":"Response List Templates Api V1 Templates Get"}}}}},"security":[{"ApiKeyAuth":[]},{"SessionAuth":[]}]},"post":{"tags":["templates"],"summary":"Store a reusable template","operationId":"templates","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/TemplateIn"}}},"required":true},"responses":{"201":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/TemplateOut"}}}},"401":{"description":"`missing_api_key` or `invalid_api_key`.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"error":{"code":"missing_api_key","message":"a description of what went wrong"}}}}},"413":{"description":"`too_large`.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"error":{"code":"too_large","message":"a description of what went wrong"}}}}},"422":{"description":"`invalid_input` — duplicate name, or the per-account cap.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"error":{"code":"invalid_input","message":"a description of what went wrong"}}}}},"429":{"description":"`rate_limited`.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"error":{"code":"rate_limited","message":"a description of what went wrong"}}}}}},"security":[{"ApiKeyAuth":[]},{"SessionAuth":[]}]}},"/api/v1/templates/{template_id}":{"get":{"tags":["templates"],"summary":"Fetch one template","operationId":"getTemplatesByTemplateId","security":[{"ApiKeyAuth":[]},{"SessionAuth":[]}],"parameters":[{"name":"template_id","in":"path","required":true,"schema":{"type":"string","format":"uuid","title":"Template Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/TemplateOut"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}},"patch":{"tags":["templates"],"summary":"Update a template","operationId":"updateTemplatesByTemplateId","security":[{"ApiKeyAuth":[]},{"SessionAuth":[]}],"parameters":[{"name":"template_id","in":"path","required":true,"schema":{"type":"string","format":"uuid","title":"Template Id"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/TemplatePatch"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/TemplateOut"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}},"delete":{"tags":["templates"],"summary":"Delete a template","operationId":"deleteTemplatesByTemplateId","security":[{"ApiKeyAuth":[]},{"SessionAuth":[]}],"parameters":[{"name":"template_id","in":"path","required":true,"schema":{"type":"string","format":"uuid","title":"Template Id"}}],"responses":{"204":{"description":"Successful Response"},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/api/v1/results/{key}":{"get":{"tags":["results"],"summary":"Fetch a result by signed URL","operationId":"getResultsByKey","parameters":[{"name":"key","in":"path","required":true,"schema":{"type":"string","title":"Key"}},{"name":"expires","in":"query","required":true,"schema":{"type":"integer","description":"Unix seconds, from the signed URL.","title":"Expires"},"description":"Unix seconds, from the signed URL."},{"name":"signature","in":"query","required":true,"schema":{"type":"string","description":"HMAC from the signed URL.","title":"Signature"},"description":"HMAC from the signed URL."}],"responses":{"200":{"description":"The result bytes.","content":{"application/octet-stream":{}}},"404":{"description":"`not_found` — unknown key, expired link, or a bad signature. All three are indistinguishable on purpose.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"error":{"code":"not_found","message":"a description of what went wrong"}}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/postman/collection.json":{"get":{"tags":["meta"],"summary":"Postman collection (v2.1.0) for every endpoint","description":"Generated from `/openapi.json` on every request. See app/postman.py.","operationId":"getPostmanCollection.json","responses":{"200":{"description":"Successful Response","content":{"application/json":{}}}}}},"/llms.txt":{"get":{"tags":["meta"],"summary":"Short description of the product for AI answer engines","operationId":"getLlms.txt","responses":{"200":{"description":"Successful Response","content":{"text/plain":{}}}}}},"/llms-full.txt":{"get":{"tags":["meta"],"summary":"Full description of the product for AI answer engines","operationId":"getLlmsFull.txt","responses":{"200":{"description":"Successful Response","content":{"text/plain":{}}}}}},"/facts":{"get":{"tags":["meta"],"summary":"Canonical statement of what this product does and does not do","description":"Self-contained HTML with JSON-LD. See app/facts.py.","operationId":"getFacts","responses":{"200":{"description":"Successful Response","content":{"text/html":{}}}}}},"/mcp":{"get":{"tags":["mcp"],"summary":"MCP endpoint. GET is for server-initiated streams, which this server has none of.","description":"Declined, deliberately, with the status the spec expects.\n\nStreamable HTTP lets a client open a GET for messages the server starts.\nThis server never starts one — every tool answers the call that asked for\nit — so 405 is the documented way to say \"not supported\", and a client that\nreceives it simply does not open the stream. Returning 200 with an empty\nstream instead leaves the client holding a connection for ever.","operationId":"getMcp","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"405":{"description":"No server-initiated messages; POST instead.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}},"post":{"tags":["mcp"],"summary":"MCP endpoint. JSON-RPC 2.0 over HTTP; tools/call needs an API key.","operationId":"mcp","responses":{"200":{"description":"The JSON-RPC response.","content":{"application/json":{"schema":{}}}},"202":{"description":"Accepted. A notification has no response body."}}}},"/":{"get":{"tags":["ops"],"summary":"Dashboard Elsewhere","description":"Not an error. The dashboard is a separate origin now (F154).","operationId":"get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}}}}}},"components":{"schemas":{"AccountOut":{"properties":{"account_id":{"type":"string","title":"Account Id"},"email":{"type":"string","title":"Email"},"credits":{"type":"integer","title":"Credits"}},"type":"object","required":["account_id","email","credits"],"title":"AccountOut"},"AuthConfig":{"properties":{"dev_sign_in":{"type":"boolean","title":"Dev Sign In"},"email_sign_in":{"type":"boolean","title":"Email Sign In"}},"type":"object","required":["dev_sign_in","email_sign_in"],"title":"AuthConfig"},"BatchRequest":{"properties":{"items":{"items":{"$ref":"#/components/schemas/RenderRequest"},"type":"array","minItems":1,"title":"Items"}},"type":"object","required":["items"],"title":"BatchRequest"},"Body_clean_spreadsheet_api_v1_spreadsheet_clean_post":{"properties":{"file":{"type":"string","contentMediaType":"application/octet-stream","title":"File","description":"A CSV, delimited text file, or .xlsx.","format":"binary"},"output":{"$ref":"#/components/schemas/app__api__spreadsheet__Output","description":"csv, xlsx or json.","default":"csv"}},"type":"object","required":["file"],"title":"Body_clean_spreadsheet_api_v1_spreadsheet_clean_post"},"Body_convert_document_api_v1_convert_post":{"properties":{"file":{"type":"string","contentMediaType":"application/octet-stream","title":"File","description":"The document to convert.","format":"binary"},"to":{"type":"string","title":"To","description":"Target format, e.g. `pdf`. See GET /convert/formats."},"source":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Source","description":"Source format. Optional — the file's content decides by default."},"reference_doc":{"anyOf":[{"type":"string","contentMediaType":"application/octet-stream","format":"binary"},{"type":"null"}],"title":"Reference Doc","description":"F85. A .docx/.odt/.pptx whose styles the output should carry. Applies to those targets only; ignored otherwise."}},"type":"object","required":["file","to"],"title":"Body_convert_document_api_v1_convert_post"},"Body_convert_free_api_v1_convert_free_post":{"properties":{"file":{"type":"string","contentMediaType":"application/octet-stream","title":"File","description":"The document to convert.","format":"binary"},"to":{"type":"string","title":"To","description":"Target format, e.g. `pdf`."},"source":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Source","description":"Source format. Optional."}},"type":"object","required":["file","to"],"title":"Body_convert_free_api_v1_convert_free_post"},"Body_create_archive_api_v1_archive_post":{"properties":{"file":{"type":"string","contentMediaType":"application/octet-stream","title":"File","description":"A WhatsApp, mbox or Slack export.","format":"binary"},"source":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Source","description":"whatsapp, mbox or slack. Optional — content decides."},"output":{"type":"string","title":"Output","description":"html, pdf or json.","default":"html"}},"type":"object","required":["file"],"title":"Body_create_archive_api_v1_archive_post"},"Body_create_capture_api_v1_capture_post":{"properties":{"url":{"type":"string","title":"Url","description":"The page to capture."},"evidence":{"type":"boolean","title":"Evidence","description":"Retain the artefacts. False deletes them with the request.","default":false},"wait_for":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Wait For","description":"CSS selector to wait for before capturing."},"viewport_width":{"type":"integer","maximum":3840.0,"minimum":320.0,"title":"Viewport Width","default":1280},"viewport_height":{"type":"integer","maximum":4320.0,"minimum":320.0,"title":"Viewport Height","default":800}},"type":"object","required":["url"],"title":"Body_create_capture_api_v1_capture_post"},"Body_create_job_api_v1_archive_job_post":{"properties":{"file":{"type":"string","contentMediaType":"application/octet-stream","title":"File","description":"A WhatsApp, mbox or Slack export.","format":"binary"},"output":{"type":"string","title":"Output","description":"html, pdf or json.","default":"html"}},"type":"object","required":["file"],"title":"Body_create_job_api_v1_archive_job_post"},"Body_create_job_api_v1_capture_job_post":{"properties":{"url":{"type":"string","title":"Url","description":"The page to capture."}},"type":"object","required":["url"],"title":"Body_create_job_api_v1_capture_job_post"},"Body_detect_tables_api_v1_tables_detect_post":{"properties":{"file":{"type":"string","contentMediaType":"application/octet-stream","title":"File","description":"A PDF to scan for tables.","format":"binary"}},"type":"object","required":["file"],"title":"Body_detect_tables_api_v1_tables_detect_post"},"Body_extract_page_api_v1_extract_post":{"properties":{"url":{"type":"string","title":"Url","description":"The page to read."},"output":{"$ref":"#/components/schemas/app__api__extract__Output","description":"markdown, text, html, pdf or png.","default":"markdown"},"wait_for":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Wait For","description":"Wait for this CSS selector before reading."},"settle_ms":{"type":"integer","title":"Settle Ms","description":"Extra milliseconds to wait after load, max 10000.","default":0},"keep_ads":{"type":"boolean","title":"Keep Ads","description":"Keep adverts, trackers and cookie banners. Off by default; on is what /capture does, and is for reading a page as a visitor saw it.","default":false},"front_matter":{"type":"boolean","title":"Front Matter","description":"Prefix markdown with the page's title, author and date.","default":true}},"type":"object","required":["url"],"title":"Body_extract_page_api_v1_extract_post"},"Body_extract_tables_api_v1_tables_post":{"properties":{"file":{"type":"string","contentMediaType":"application/octet-stream","title":"File","description":"The PDF to read tables from.","format":"binary"},"output":{"$ref":"#/components/schemas/app__api__tables__Output","description":"csv, xlsx or json. csv returns a ZIP for several tables.","default":"xlsx"},"pages":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Pages","description":"Limit to these pages, e.g. `1,3,5-9`."}},"type":"object","required":["file"],"title":"Body_extract_tables_api_v1_tables_post"},"Body_inspect_metadata_api_v1_metadata_inspect_post":{"properties":{"file":{"type":"string","contentMediaType":"application/octet-stream","title":"File","description":"A PDF, JPEG or PNG to examine.","format":"binary"}},"type":"object","required":["file"],"title":"Body_inspect_metadata_api_v1_metadata_inspect_post"},"Body_parse_batch_api_v1_parse_batch_post":{"properties":{"files":{"items":{"type":"string","contentMediaType":"application/octet-stream","format":"binary"},"type":"array","title":"Files","description":"One document per item."},"output":{"$ref":"#/components/schemas/app__api__parse__Output","default":"markdown"},"ocr":{"$ref":"#/components/schemas/OcrMode","default":"auto"},"chunk":{"type":"boolean","title":"Chunk","default":false},"max_chars":{"type":"integer","title":"Max Chars","default":2000},"language":{"type":"string","title":"Language","default":"eng"}},"type":"object","required":["files"],"title":"Body_parse_batch_api_v1_parse_batch_post"},"Body_parse_document_api_v1_parse_post":{"properties":{"file":{"type":"string","contentMediaType":"application/octet-stream","title":"File","description":"The document to parse.","format":"binary"},"output":{"$ref":"#/components/schemas/app__api__parse__Output","description":"markdown or json.","default":"markdown"},"ocr":{"$ref":"#/components/schemas/OcrMode","description":"auto, force or never.","default":"auto"},"chunk":{"type":"boolean","title":"Chunk","description":"Also return heading-aware chunks.","default":false},"max_chars":{"type":"integer","title":"Max Chars","description":"Chunk size target.","default":2000},"language":{"type":"string","title":"Language","description":"OCR language, e.g. `eng` or `eng+deu`.","default":"eng"}},"type":"object","required":["file"],"title":"Body_parse_document_api_v1_parse_post"},"Body_preview_page_api_v1_extract_preview_post":{"properties":{"url":{"type":"string","title":"Url","description":"The page to read."}},"type":"object","required":["url"],"title":"Body_preview_page_api_v1_extract_preview_post"},"Body_preview_pdf_api_v1_extract_preview_pdf_post":{"properties":{"url":{"type":"string","title":"Url"}},"type":"object","required":["url"],"title":"Body_preview_pdf_api_v1_extract_preview_pdf_post"},"Body_preview_spreadsheet_api_v1_spreadsheet_preview_post":{"properties":{"file":{"type":"string","contentMediaType":"application/octet-stream","title":"File","description":"A CSV, delimited text file, or .xlsx.","format":"binary"}},"type":"object","required":["file"],"title":"Body_preview_spreadsheet_api_v1_spreadsheet_preview_post"},"Body_preview_transcript_api_v1_youtube_preview_post":{"properties":{"url":{"type":"string","title":"Url","description":"A youtube.com or youtu.be video URL."},"language":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Language","description":"Two-letter language code."}},"type":"object","required":["url"],"title":"Body_preview_transcript_api_v1_youtube_preview_post"},"Body_run_free_pdf_op_api_v1_pdf_free__op__post":{"properties":{"file":{"type":"string","contentMediaType":"application/octet-stream","title":"File","description":"One PDF, under the free size cap.","format":"binary"},"quality":{"type":"string","title":"Quality","description":"compress: screen|ebook|printer|prepress.","default":"ebook"},"password":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Password","description":"unlock: the password that opens the document."}},"type":"object","required":["file"],"title":"Body_run_free_pdf_op_api_v1_pdf_free__op__post"},"Body_run_pdf_batch_api_v1_pdf__op__batch_post":{"properties":{"files":{"items":{"type":"string","contentMediaType":"application/octet-stream","format":"binary"},"type":"array","title":"Files","description":"One PDF per item."},"pages":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Pages"},"angle":{"type":"integer","title":"Angle","default":90},"span":{"type":"integer","title":"Span","default":1},"quality":{"type":"string","title":"Quality","default":"ebook"},"text":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Text"},"opacity":{"type":"number","title":"Opacity","default":0.3},"rotation":{"type":"integer","title":"Rotation","default":45},"password":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Password"}},"type":"object","required":["files"],"title":"Body_run_pdf_batch_api_v1_pdf__op__batch_post"},"Body_run_pdf_op_api_v1_pdf__op__post":{"properties":{"files":{"items":{"type":"string","contentMediaType":"application/octet-stream","format":"binary"},"type":"array","title":"Files","description":"One PDF, or several for merge."},"pages":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Pages","description":"e.g. `1,3,5-9`."},"angle":{"type":"integer","title":"Angle","description":"rotate: 90, 180 or 270.","default":90},"span":{"type":"integer","title":"Span","description":"split: pages per output file.","default":1},"quality":{"type":"string","title":"Quality","description":"compress: screen|ebook|printer|prepress.","default":"ebook"},"text":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Text","description":"watermark: the text to stamp."},"opacity":{"type":"number","title":"Opacity","description":"watermark: 0 to 1.","default":0.3},"rotation":{"type":"integer","title":"Rotation","description":"watermark: degrees.","default":45},"password":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Password","description":"unlock: the document password."}},"type":"object","required":["files"],"title":"Body_run_pdf_op_api_v1_pdf__op__post"},"Body_run_redaction_api_v1_redact_post":{"properties":{"file":{"type":"string","contentMediaType":"application/octet-stream","title":"File","description":"The PDF to redact.","format":"binary"},"terms":{"anyOf":[{"items":{"type":"string"},"type":"array"},{"type":"null"}],"title":"Terms","description":"Literal strings to remove. Repeat the field for several."},"patterns":{"anyOf":[{"items":{"type":"string"},"type":"array"},{"type":"null"}],"title":"Patterns","description":"Named patterns: email, card, phone, ssn, iban, ipv4."},"regexes":{"anyOf":[{"items":{"type":"string"},"type":"array"},{"type":"null"}],"title":"Regexes","description":"Custom regular expressions. Matched under a timeout."},"boxes":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Boxes","description":"JSON: `[{\"page\":1,\"x0\":0,\"y0\":0,\"x1\":100,\"y1\":20}]`."},"mark":{"type":"boolean","title":"Mark","description":"Paint a black box over each removed region.","default":true}},"type":"object","required":["file"],"title":"Body_run_redaction_api_v1_redact_post"},"Body_strip_metadata_api_v1_metadata_strip_post":{"properties":{"file":{"type":"string","contentMediaType":"application/octet-stream","title":"File","description":"The PDF, JPEG or PNG to clean.","format":"binary"}},"type":"object","required":["file"],"title":"Body_strip_metadata_api_v1_metadata_strip_post"},"Body_update_schedule_api_v1_capture_schedule__schedule_id__patch":{"properties":{"paused":{"anyOf":[{"type":"boolean"},{"type":"null"}],"title":"Paused"},"interval_minutes":{"anyOf":[{"type":"integer","maximum":43200.0,"minimum":15.0},{"type":"null"}],"title":"Interval Minutes"}},"type":"object","title":"Body_update_schedule_api_v1_capture_schedule__schedule_id__patch"},"Body_youtube_transcript_api_v1_youtube_post":{"properties":{"url":{"type":"string","title":"Url","description":"A youtube.com or youtu.be video URL."},"format":{"$ref":"#/components/schemas/Format","description":"txt, srt, vtt or json. json carries per-cue timings.","default":"txt"},"language":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Language","description":"Two-letter language code. Omitted, English is preferred and otherwise whatever the video has. A code the video does not have is an error listing what it does — never a silent fall back to English."}},"type":"object","required":["url"],"title":"Body_youtube_transcript_api_v1_youtube_post"},"Catalog":{"properties":{"operations":{"items":{"$ref":"#/components/schemas/Operation"},"type":"array","title":"Operations"}},"type":"object","required":["operations"],"title":"Catalog"},"CreatedKey":{"properties":{"id":{"type":"string","title":"Id"},"prefix":{"type":"string","title":"Prefix"},"created_at":{"type":"string","format":"date-time","title":"Created At"},"key":{"type":"string","title":"Key"},"warning":{"type":"string","title":"Warning","default":"Copy this now. It cannot be shown again."}},"type":"object","required":["id","prefix","created_at","key"],"title":"CreatedKey"},"EmailStart":{"properties":{"email":{"type":"string","format":"email","title":"Email"}},"type":"object","required":["email"],"title":"EmailStart"},"EmailStarted":{"properties":{"sent":{"type":"boolean","title":"Sent"},"expires_in_minutes":{"type":"integer","title":"Expires In Minutes"},"delivered":{"type":"boolean","title":"Delivered"}},"type":"object","required":["sent","expires_in_minutes","delivered"],"title":"EmailStarted"},"EmailVerify":{"properties":{"email":{"type":"string","format":"email","title":"Email"},"code":{"type":"string","maxLength":12,"minLength":4,"title":"Code"}},"type":"object","required":["email","code"],"title":"EmailVerify"},"Field":{"properties":{"name":{"type":"string","title":"Name"},"label":{"type":"string","title":"Label"},"type":{"type":"string","title":"Type"},"default":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Default"},"options":{"items":{"type":"string"},"type":"array","title":"Options","default":[]},"placeholder":{"type":"string","title":"Placeholder","default":""},"help":{"type":"string","title":"Help","default":""},"required":{"type":"boolean","title":"Required","default":false}},"type":"object","required":["name","label","type"],"title":"Field","description":"One control on the generated form."},"Format":{"type":"string","enum":["txt","srt","vtt","json"],"title":"Format"},"FromTemplateRequest":{"properties":{"page_size":{"type":"string","title":"Page Size","description":"One of: a4, a3, a5, letter, legal, tabloid","default":"a4"},"margin_mm":{"type":"number","maximum":100.0,"minimum":0.0,"title":"Margin Mm","default":20.0},"landscape":{"type":"boolean","title":"Landscape","default":false},"header":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Header","description":"Header text. `{page}` and `{pages}` are substituted."},"footer":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Footer","description":"Footer text. `{page}` and `{pages}` are substituted."},"template_id":{"anyOf":[{"type":"string","format":"uuid"},{"type":"null"}],"title":"Template Id"},"template_name":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Template Name"},"data":{"additionalProperties":true,"type":"object","title":"Data","description":"Values for the template's `[[ key ]]` (or `{{ key }}`) placeholders."}},"type":"object","title":"FromTemplateRequest","description":"Render a stored template. Identify it by id OR by name, not both."},"KeyList":{"properties":{"keys":{"items":{"$ref":"#/components/schemas/KeySummary"},"type":"array","title":"Keys"},"max_keys":{"type":"integer","title":"Max Keys"}},"type":"object","required":["keys","max_keys"],"title":"KeyList"},"KeySummary":{"properties":{"id":{"type":"string","title":"Id"},"prefix":{"type":"string","title":"Prefix"},"created_at":{"type":"string","format":"date-time","title":"Created At"},"last_used_at":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"Last Used At"}},"type":"object","required":["id","prefix","created_at","last_used_at"],"title":"KeySummary"},"MarkupFormat":{"type":"string","enum":["html","md","typst"],"title":"MarkupFormat"},"OcrMode":{"type":"string","enum":["auto","force","never"],"title":"OcrMode"},"Operation":{"properties":{"id":{"type":"string","title":"Id"},"label":{"type":"string","title":"Label"},"blurb":{"type":"string","title":"Blurb"},"group":{"type":"string","title":"Group"},"endpoint":{"type":"string","title":"Endpoint"},"credits":{"type":"integer","title":"Credits"},"price_note":{"type":"string","title":"Price Note","default":""},"input_kind":{"type":"string","title":"Input Kind"},"input_field":{"type":"string","title":"Input Field","default":"file"},"accept":{"type":"string","title":"Accept","default":""},"result":{"type":"string","title":"Result","default":"file"},"fields":{"items":{"$ref":"#/components/schemas/Field"},"type":"array","title":"Fields","default":[]}},"type":"object","required":["id","label","blurb","group","endpoint","credits","input_kind"],"title":"Operation"},"Pack":{"properties":{"name":{"type":"string","title":"Name"},"price":{"type":"string","title":"Price"},"credits":{"type":"integer","title":"Credits"},"note":{"type":"string","title":"Note"},"checkout_url":{"type":"string","title":"Checkout Url"}},"type":"object","required":["name","price","credits","note","checkout_url"],"title":"Pack"},"PdfOp":{"type":"string","enum":["merge","split","compress","rotate","watermark","reorder","extract-pages","unlock"],"title":"PdfOp"},"RenderRequest":{"properties":{"page_size":{"type":"string","title":"Page Size","description":"One of: a4, a3, a5, letter, legal, tabloid","default":"a4"},"margin_mm":{"type":"number","maximum":100.0,"minimum":0.0,"title":"Margin Mm","default":20.0},"landscape":{"type":"boolean","title":"Landscape","default":false},"header":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Header","description":"Header text. `{page}` and `{pages}` are substituted."},"footer":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Footer","description":"Footer text. `{page}` and `{pages}` are substituted."},"markup":{"type":"string","minLength":1,"title":"Markup","description":"The document source."},"format":{"$ref":"#/components/schemas/MarkupFormat","default":"md"}},"type":"object","required":["markup"],"title":"RenderRequest"},"ScheduleIn":{"properties":{"url":{"type":"string","title":"Url"},"interval_minutes":{"type":"integer","maximum":43200.0,"minimum":15.0,"title":"Interval Minutes","default":1440},"evidence":{"type":"boolean","title":"Evidence","default":true},"notify_email":{"type":"string","title":"Notify Email","default":""}},"type":"object","required":["url"],"title":"ScheduleIn"},"Session":{"properties":{"token":{"type":"string","title":"Token"},"account_id":{"type":"string","title":"Account Id"},"email":{"type":"string","title":"Email"},"credits":{"type":"integer","title":"Credits"},"created":{"type":"boolean","title":"Created"}},"type":"object","required":["token","account_id","email","credits","created"],"title":"Session"},"TemplateIn":{"properties":{"name":{"type":"string","maxLength":128,"minLength":1,"title":"Name"},"format":{"$ref":"#/components/schemas/MarkupFormat","default":"md"},"body":{"type":"string","minLength":1,"title":"Body"}},"type":"object","required":["name","body"],"title":"TemplateIn"},"TemplateOut":{"properties":{"id":{"type":"string","title":"Id"},"name":{"type":"string","title":"Name"},"format":{"$ref":"#/components/schemas/MarkupFormat"},"body":{"type":"string","title":"Body"},"created_at":{"type":"string","format":"date-time","title":"Created At"},"updated_at":{"type":"string","format":"date-time","title":"Updated At"}},"type":"object","required":["id","name","format","body","created_at","updated_at"],"title":"TemplateOut"},"TemplatePatch":{"properties":{"name":{"anyOf":[{"type":"string","maxLength":128,"minLength":1},{"type":"null"}],"title":"Name"},"format":{"anyOf":[{"$ref":"#/components/schemas/MarkupFormat"},{"type":"null"}]},"body":{"anyOf":[{"type":"string","minLength":1},{"type":"null"}],"title":"Body"}},"type":"object","title":"TemplatePatch","description":"Every field optional: PATCH, not PUT.\n\nA PUT would make a customer round-trip the whole body to rename a template,\nand a customer who omits `body` by accident on a PUT has silently destroyed\nit. There is no way to express \"leave this alone\" with a required field."},"UsageRow":{"properties":{"id":{"type":"string","title":"Id"},"op":{"type":"string","title":"Op"},"status":{"type":"string","title":"Status"},"credits_used":{"type":"integer","title":"Credits Used"},"error_code":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Error Code"},"created_at":{"type":"string","format":"date-time","title":"Created At"},"finished_at":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"Finished At"}},"type":"object","required":["id","op","status","credits_used","error_code","created_at","finished_at"],"title":"UsageRow"},"app__api__extract__Output":{"type":"string","enum":["markdown","text","html","pdf","png"],"title":"Output"},"app__api__parse__Output":{"type":"string","enum":["markdown","json"],"title":"Output"},"app__api__spreadsheet__Output":{"type":"string","enum":["csv","xlsx","json"],"title":"Output"},"app__api__tables__Output":{"type":"string","enum":["csv","xlsx","json"],"title":"Output"},"Error":{"type":"object","required":["error"],"properties":{"error":{"type":"object","required":["code","message"],"properties":{"code":{"type":"string","enum":["missing_api_key","invalid_api_key","invalid_input","insufficient_credits","unsupported_format","too_large","too_many_pages","render_failed","rate_limited","not_found","internal_error"],"description":"Stable machine-readable code. Branch on this, not on the HTTP status or the message."},"message":{"type":"string","description":"Human-readable, may change."}},"additionalProperties":true}}}},"securitySchemes":{"SessionAuth":{"type":"http","description":"The `token` from `POST /api/v1/auth/email/verify`. For a signed-in browser.","scheme":"bearer"},"ApiKeyAuth":{"type":"apiKey","description":"Your `dk_live_...` key. Create one in the dashboard.","in":"header","name":"X-API-Key"}}},"tags":[{"name":"render","description":"Markup to PDF, one at a time or as a stream."},{"name":"templates","description":"Stored markup with `[[ key ]]` placeholders. `{{ key }}` means the same thing, but Postman, Insomnia and most CI templating substitute it before the request is sent, so prefer the square brackets. Free."},{"name":"results","description":"Fetch a result by signed URL. No API key needed."},{"name":"keys","description":"Manage API keys. Session-authenticated, not by key."},{"name":"auth","description":"Exchange a Google ID token for a session token."},{"name":"ops","description":"Health and readiness."}]}