{"openapi":"3.1.0","info":{"title":"PDFMint API","version":"1.0.0","description":"HTML, Markdown, URL and template rendering to PDF and images over a simple JSON API. The human-readable reference lives at https://pdf.mintapis.com/docs; this document is the machine-readable OpenAPI 3.1 description of the same endpoints. Authentication uses either `Authorization: Bearer <key>` or `x-api-key: <key>`; keys start with `pm_live_`. Every authenticated endpoint is rate-limited to 120 requests per minute per account with a burst of 30 (headers X-RateLimit-Limit, X-RateLimit-Burst, X-RateLimit-Remaining; a 429 carries Retry-After). App-level errors that are not tied to one operation — `unknown_endpoint` (404), `invalid_json` (400), `request_too_large` (413) and `internal_error` (500) — are documented once in the top-level `x-app-level-errors` section; every operation-specific error code is listed on the operation it can hit."},"servers":[{"url":"https://pdf.mintapis.com"},{"url":"https://pdfmint-b9tt.onrender.com"}],"tags":[{"name":"render"},{"name":"files"},{"name":"account"},{"name":"templates"},{"name":"keys"},{"name":"jobs"},{"name":"recovery"}],"paths":{"/v1/me":{"get":{"tags":["account"],"summary":"Account balance and plan","description":"Returns the authenticated account’s plan, monthly credit usage and when the period resets (on the 1st of the month, UTC).","security":[{"bearerAuth":[]},{"apiKeyAuth":[]}],"responses":{"200":{"description":"","content":{"application/json":{"schema":{"type":"object","properties":{"email":{"type":"string","format":"email"},"plan":{"type":"string"},"plan_name":{"type":"string"},"credits_limit":{"type":"integer"},"credits_used":{"type":"integer"},"credits_remaining":{"type":"integer"},"period_resets_at":{"type":"string","format":"date-time"},"dashboard_url":{"type":"string","format":"uri"}},"required":["email","plan","plan_name","credits_limit","credits_used","credits_remaining","period_resets_at","dashboard_url"]}}}},"401":{"description":"`missing_api_key` — No API key was sent (Authorization: Bearer or x-api-key).\n\n`invalid_api_key` — The API key is not valid or has been revoked.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"x-error-codes":[{"code":"missing_api_key","meaning":"No API key was sent (Authorization: Bearer or x-api-key)."},{"code":"invalid_api_key","meaning":"The API key is not valid or has been revoked."}]},"429":{"description":"`rate_limited` — Too many requests: 120 per minute per account with a burst of 30. Retry-After says how long to wait.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"x-error-codes":[{"code":"rate_limited","meaning":"Too many requests: 120 per minute per account with a burst of 30. Retry-After says how long to wait."}]}}}},"/v1/demo/pdf":{"post":{"tags":["render"],"summary":"Keyless demo render (HTML only)","description":"Renders HTML to PDF without an API key. Only \"html\" is honoured; anything else in the body is ignored. Capped at 16 KB of HTML and 5 renders per hour per client address (X-PDFMint-Demo-Remaining header).","security":[],"requestBody":{"description":"","content":{"application/json":{"schema":{"type":"object","required":["html"],"properties":{"html":{"type":"string"}}}}}},"responses":{"200":{"description":"The rendered PDF (hello.pdf).","content":{"application/pdf":{"schema":{"type":"string","format":"binary"}}}},"400":{"description":"`missing_content` — send the markup in \"html\".","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"x-error-codes":[{"code":"missing_content","meaning":"Nothing to render: send exactly one of \"html\", \"markdown\", \"url\" or \"template\"."}]},"413":{"description":"`demo_payload_too_large` — the demo accepts 16 KB of HTML.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"x-error-codes":[{"code":"demo_payload_too_large","meaning":"The demo accepts 16 KB of HTML; the request was larger."}]},"429":{"description":"`demo_limit_reached` — 5 renders an hour from one address; Retry-After says when to retry.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"x-error-codes":[{"code":"demo_limit_reached","meaning":"The keyless demo allows 5 renders an hour from one address."}]},"500":{"description":"`renderer_crashed` or `render_failed` — the renderer stopped while producing the document; nothing was charged.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"x-error-codes":[{"code":"renderer_crashed","meaning":"The renderer stopped before the document was finished — most likely the page is too large or too complex; on an async job this happens after two attempts."},{"code":"render_failed","meaning":"The renderer could not produce the document; a failed async job carries this code in its error object."}]},"503":{"description":"`renderer_busy` — the render queue is full; retry in a few seconds.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"x-error-codes":[{"code":"renderer_busy","meaning":"PDFMint is at capacity: the render queue is full. Retry in a few seconds."}]},"504":{"description":"`render_timeout` — rendering exceeded the fixed demo timeout.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"x-error-codes":[{"code":"render_timeout","meaning":"Rendering exceeded the \"timeout\" while loading or printing the document."}]}}}},"/v1/pdf":{"post":{"tags":["render"],"summary":"Render HTML, Markdown, a URL or a stored template to PDF","description":"Costs 1 credit. Synchronous by default (binary/url/base64 output); with \"async\": true or a \"webhookUrl\" it queues a job instead and returns 202 with a status_url for GET /v1/jobs/{id}. Placeholders like {{name}} are filled from \"data\" and checked by strict mode.","security":[{"bearerAuth":[]},{"apiKeyAuth":[]}],"requestBody":{"description":"","content":{"application/json":{"schema":{"type":"object","description":"Exactly one content field must be set. Every PDF option below is accepted flat OR nested inside \"options\" (a flat key wins over the same key in \"options\").","properties":{"html":{"type":"string","description":"HTML markup to render. May contain {{placeholders}} filled from \"data\"."},"markdown":{"type":"string","description":"Markdown to convert and render. May contain {{placeholders}}."},"url":{"type":"string","format":"uri","description":"Public http(s) URL to render; private and link-local addresses are blocked."},"template":{"type":"string","description":"Name of a stored template (see /v1/templates). Filled from \"data\"; its stored options are defaults overridden by the request."},"data":{"description":"Values for {{placeholders}}; an object, or a string containing JSON.","anyOf":[{"type":"object"},{"type":"string"}]},"strict":{"type":"boolean","default":false,"description":"Refuse (400) instead of returning a blank, placeholder-laden or escaped-markup document."},"output":{"type":"string","enum":["binary","url","base64"],"default":"binary","description":"\"binary\" (default) returns the PDF bytes; \"url\" hosts the file and returns a temporary link; \"base64\" returns the bytes inside the JSON."},"filename":{"type":"string","description":"Download name for binary output; \".pdf\" is appended if missing."},"options":{"type":"object","description":"PDF page options. On /v1/pdf they are accepted flat in the body or nested under \"options\" (flat wins on conflict); /v1/image and /v1/merge accept the same flat keys. Values are validated against src/options.js: unknown keys are refused, not ignored.","properties":{"format":{"type":"string","enum":["A0","A1","A2","A3","A4","A5","A6","Letter","Legal","Tabloid","Ledger"],"default":"A4"},"width":{"description":"Custom page width; a number is treated as px. Requires \"height\".","anyOf":[{"type":"string"},{"type":"number"}]},"height":{"description":"Custom page height; a number is treated as px. Requires \"width\".","anyOf":[{"type":"string"},{"type":"number"}]},"landscape":{"type":"boolean","default":false},"margin":{"description":"A CSS length (default 12mm per side) or an object of per-side lengths. \"\" means 0 on every side.","anyOf":[{"type":"string"},{"type":"number"},{"type":"object","properties":{"top":{"type":"string"},"right":{"type":"string"},"bottom":{"type":"string"},"left":{"type":"string"}}}]},"scale":{"type":"number","minimum":0.1,"maximum":2,"default":1},"printBackground":{"type":"boolean","default":true},"headerHtml":{"type":"string","description":"Alias: \"headerTemplate\". Rendered on every page; enables header/footer mode."},"footerHtml":{"type":"string","description":"Alias: \"footerTemplate\". Rendered on every page; enables header/footer mode."},"pageNumbers":{"description":"true builds a default \"Page {page} of {total}\" footer; a string is a template with {page}, {total}, {date}, {title}, {url}.","anyOf":[{"type":"boolean"},{"type":"string"}]},"pageRanges":{"type":"string","description":"Comma-separated pages and ranges, 1-based, e.g. \"1-5, 8, 11-13\"."},"coverHtml":{"type":"string","description":"HTML for a cover rendered as the first page(s), ahead of the document. The cover gets NO header, footer or page numbers; content numbering starts at 1 with {total} counting content pages only. {{placeholders}} are filled from \"data\". Inherits javascript, emulateDarkMode and headers; \"waitFor\" applies to the content only. Cannot be combined with \"pageRanges\" (invalid_option)."},"mediaType":{"type":"string","enum":["print","screen"],"default":"print"},"preferCssPageSize":{"type":"boolean","default":false,"description":"Alias: \"preferCSSPageSize\"."},"tagged":{"type":"boolean","default":true},"outline":{"type":"boolean","default":false}}},"timeout":{"description":"Render timeout in ms; minimum 1000, capped at the server maximum (excess is clamped with a warning). Alias: \"timeoutMs\".","anyOf":[{"type":"number"},{"type":"string"}]},"timeoutMs":{"description":"Alias of \"timeout\".","anyOf":[{"type":"number"},{"type":"string"}]},"waitFor":{"description":"Wait for a selector, a fixed delay, or network idle before printing. Aliases: \"waitUntil\", \"wait\", \"delay\".","anyOf":[{"type":"string"},{"type":"number"}]},"javascript":{"type":"boolean","description":"Allow page JavaScript. Alias accepted on /v1/pdf only as a flat key."},"emulateDarkMode":{"type":"boolean"},"headers":{"type":"object","additionalProperties":{"type":"string"},"description":"Extra HTTP headers when rendering a \"url\"."},"css":{"type":"string","description":"Extra CSS (applied with \"markdown\"; honoured for html by the renderer)."},"googleFonts":{"type":"string","description":"Google Fonts to load before rendering."},"metadata":{"type":"object","description":"PDF metadata, e.g. {\"title\": \"...\", \"author\": \"...\"}."},"password":{"type":"string","description":"Encrypt the PDF with this user password. Alias of \"encrypt\"."},"ownerPassword":{"type":"string"},"allowPrinting":{"type":"boolean","default":true},"allowCopying":{"type":"boolean","default":false},"watermark":{"description":"A watermark string, or {\"text\", ...}. Placeholders in \"text\" are filled from \"data\".","anyOf":[{"type":"string"},{"type":"object"}]},"debug":{"type":"boolean","default":false,"description":"Return page errors in the X-PDFMint-Page-Errors header and the JSON response."},"expiresInMinutes":{"description":"Hosted-file TTL in minutes for output \"url\" (clamped to the server minimum of 1 and maximum). Alias: \"expiration\".","anyOf":[{"type":"number"},{"type":"string"}]},"expiration":{"description":"Alias of \"expiresInMinutes\".","anyOf":[{"type":"number"},{"type":"string"}]},"async":{"type":"boolean","default":false,"description":"Queue the render and return 202 with a job; poll GET /v1/jobs/{id}."},"webhookUrl":{"type":"string","description":"Public URL notified when an async job finishes (implies async). Aliases: \"webhook_url\", \"callback\", \"callbackUrl\", \"webhook\"."},"webhook_url":{"type":"string","description":"Alias of \"webhookUrl\"."},"title":{"type":"string","description":"Document title (used for the markdown conversion when no metadata title is set)."},"format":{"type":"string","enum":["A0","A1","A2","A3","A4","A5","A6","Letter","Legal","Tabloid","Ledger"],"default":"A4"},"width":{"description":"Custom page width; a number is treated as px. Requires \"height\".","anyOf":[{"type":"string"},{"type":"number"}]},"height":{"description":"Custom page height; a number is treated as px. Requires \"width\".","anyOf":[{"type":"string"},{"type":"number"}]},"landscape":{"type":"boolean","default":false},"margin":{"description":"A CSS length (default 12mm per side) or an object of per-side lengths. \"\" means 0 on every side.","anyOf":[{"type":"string"},{"type":"number"},{"type":"object","properties":{"top":{"type":"string"},"right":{"type":"string"},"bottom":{"type":"string"},"left":{"type":"string"}}}]},"scale":{"type":"number","minimum":0.1,"maximum":2,"default":1},"printBackground":{"type":"boolean","default":true},"headerHtml":{"type":"string","description":"Alias: \"headerTemplate\". Rendered on every page; enables header/footer mode."},"footerHtml":{"type":"string","description":"Alias: \"footerTemplate\". Rendered on every page; enables header/footer mode."},"headerTemplate":{"type":"string","description":"Alias of \"headerHtml\"."},"footerTemplate":{"type":"string","description":"Alias of \"footerHtml\"."},"pageNumbers":{"description":"true builds a default \"Page {page} of {total}\" footer; a string is a template with {page}, {total}, {date}, {title}, {url}.","anyOf":[{"type":"boolean"},{"type":"string"}]},"pageRanges":{"type":"string","description":"Comma-separated pages and ranges, 1-based, e.g. \"1-5, 8, 11-13\"."},"coverHtml":{"type":"string","description":"HTML for a cover rendered as the first page(s), ahead of the document. The cover gets NO header, footer or page numbers; content numbering starts at 1 with {total} counting content pages only. {{placeholders}} are filled from \"data\". Inherits javascript, emulateDarkMode and headers; \"waitFor\" applies to the content only. Cannot be combined with \"pageRanges\" (invalid_option)."},"mediaType":{"type":"string","enum":["print","screen"],"default":"print"},"preferCssPageSize":{"type":"boolean","default":false,"description":"Alias: \"preferCSSPageSize\"."},"preferCSSPageSize":{"type":"boolean","description":"Alias of \"preferCssPageSize\"."},"tagged":{"type":"boolean","default":true},"outline":{"type":"boolean","default":false}}}}}},"responses":{"200":{"description":"The rendered document. Binary mode returns application/pdf bytes; url and base64 return JSON.","content":{"application/pdf":{"schema":{"type":"string","format":"binary"}},"application/json":{"schema":{"$ref":"#/components/schemas/FileJson"}}}},"202":{"description":"","content":{"application/json":{"schema":{"type":"object","properties":{"job_id":{"type":"string"},"status":{"type":"string","enum":["queued"]},"status_url":{"type":"string","format":"uri","description":"GET /v1/jobs/{id} for the same job."},"webhook_url":{"type":["string","null"]},"credits_remaining":{"type":"integer"}},"required":["job_id","status","status_url","credits_remaining"]}}}},"400":{"description":"`missing_content` — Nothing to render: send exactly one of \"html\", \"markdown\", \"url\" or \"template\".\n\n`ambiguous_content` — More than one of \"html\", \"markdown\", \"url\" or \"template\" was sent.\n\n`unknown_field` — The request contains a field the endpoint does not accept.\n\n`invalid_option` — An option value is malformed or out of range.\n\n`invalid_data` — The \"data\" field is a string but is not valid JSON.\n\n`invalid_url` — The URL field is not a valid URL.\n\n`unsupported_url_scheme` — The URL field must use http:// or https://.\n\n`private_address_blocked` — The URL points at (or resolves to) a private, loopback or link-local address; SSRF is blocked.\n\n`dns_failed` — The host in the URL could not be resolved.\n\n`html_too_large` — The HTML exceeds the maximum document size.\n\n`template_not_found` — No stored template with that name exists on this account.\n\n`unresolved_placeholders` — The template uses placeholders that \"data\" does not provide (strict mode).\n\n`invalid_placeholder_value` — A placeholder value would print as \"[object Object]\" (strict mode).\n\n`blank_document` — The document has no visible content (strict mode).\n\n`unrendered_markup` — The markup was printed as text instead of being applied (strict mode).\n\n`file_too_large` — The generated file is over the hosted-file size limit; use output \"binary\".","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"x-error-codes":[{"code":"missing_content","meaning":"Nothing to render: send exactly one of \"html\", \"markdown\", \"url\" or \"template\"."},{"code":"ambiguous_content","meaning":"More than one of \"html\", \"markdown\", \"url\" or \"template\" was sent."},{"code":"unknown_field","meaning":"The request contains a field the endpoint does not accept."},{"code":"invalid_option","meaning":"An option value is malformed or out of range."},{"code":"invalid_data","meaning":"The \"data\" field is a string but is not valid JSON."},{"code":"invalid_url","meaning":"The URL field is not a valid URL."},{"code":"unsupported_url_scheme","meaning":"The URL field must use http:// or https://."},{"code":"private_address_blocked","meaning":"The URL points at (or resolves to) a private, loopback or link-local address; SSRF is blocked."},{"code":"dns_failed","meaning":"The host in the URL could not be resolved."},{"code":"html_too_large","meaning":"The HTML exceeds the maximum document size."},{"code":"template_not_found","meaning":"No stored template with that name exists on this account."},{"code":"unresolved_placeholders","meaning":"The template uses placeholders that \"data\" does not provide (strict mode)."},{"code":"invalid_placeholder_value","meaning":"A placeholder value would print as \"[object Object]\" (strict mode)."},{"code":"blank_document","meaning":"The document has no visible content (strict mode)."},{"code":"unrendered_markup","meaning":"The markup was printed as text instead of being applied (strict mode)."},{"code":"file_too_large","meaning":"The generated file is over the hosted-file size limit; use output \"binary\"."}]},"401":{"description":"`missing_api_key` — No API key was sent (Authorization: Bearer or x-api-key).\n\n`invalid_api_key` — The API key is not valid or has been revoked.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"x-error-codes":[{"code":"missing_api_key","meaning":"No API key was sent (Authorization: Bearer or x-api-key)."},{"code":"invalid_api_key","meaning":"The API key is not valid or has been revoked."}]},"402":{"description":"`plan_required` — The account has no plan, so it cannot render anything yet.\n\n`quota_exceeded` — All documents included in the monthly plan have been used.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"x-error-codes":[{"code":"plan_required","meaning":"The account has no plan, so it cannot render anything yet."},{"code":"quota_exceeded","meaning":"All documents included in the monthly plan have been used."}]},"429":{"description":"`rate_limited` — Too many requests: 120 per minute per account with a burst of 30. Retry-After says how long to wait.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"x-error-codes":[{"code":"rate_limited","meaning":"Too many requests: 120 per minute per account with a burst of 30. Retry-After says how long to wait."}]},"500":{"description":"`renderer_crashed` — The renderer stopped before the document was finished — most likely the page is too large or too complex; on an async job this happens after two attempts.\n\n`render_failed` — The renderer could not produce the document; a failed async job carries this code in its error object.\n\n`encryption_failed` — The PDF was rendered but could not be password-protected.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"x-error-codes":[{"code":"renderer_crashed","meaning":"The renderer stopped before the document was finished — most likely the page is too large or too complex; on an async job this happens after two attempts."},{"code":"render_failed","meaning":"The renderer could not produce the document; a failed async job carries this code in its error object."},{"code":"encryption_failed","meaning":"The PDF was rendered but could not be password-protected."}]},"501":{"description":"`encryption_unavailable` — Password protection is not available on this deployment (the qpdf binary is missing).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"x-error-codes":[{"code":"encryption_unavailable","meaning":"Password protection is not available on this deployment (the qpdf binary is missing)."}]},"502":{"description":"`url_unreachable` — The URL could not be loaded: the host did not resolve, refused the connection or has an invalid TLS certificate.\n\n`url_http_error` — The URL was reached but answered with an HTTP error status; details.upstream_status carries it.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"x-error-codes":[{"code":"url_unreachable","meaning":"The URL could not be loaded: the host did not resolve, refused the connection or has an invalid TLS certificate."},{"code":"url_http_error","meaning":"The URL was reached but answered with an HTTP error status; details.upstream_status carries it."}]},"503":{"description":"`renderer_busy` — PDFMint is at capacity: the render queue is full. Retry in a few seconds.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"x-error-codes":[{"code":"renderer_busy","meaning":"PDFMint is at capacity: the render queue is full. Retry in a few seconds."}]},"504":{"description":"`render_timeout` — Rendering exceeded the \"timeout\" while loading or printing the document.\n\n`wait_for_timeout` — The selector passed as \"waitFor\" did not appear on the page before the timeout elapsed.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"x-error-codes":[{"code":"render_timeout","meaning":"Rendering exceeded the \"timeout\" while loading or printing the document."},{"code":"wait_for_timeout","meaning":"The selector passed as \"waitFor\" did not appear on the page before the timeout elapsed."}]}}}},"/v1/image":{"post":{"tags":["render"],"summary":"Render HTML, Markdown or a URL to a PNG or JPEG","description":"Costs 1 credit. Screenshots the rendered page; saved templates are not supported here.","security":[{"bearerAuth":[]},{"apiKeyAuth":[]}],"requestBody":{"description":"","content":{"application/json":{"schema":{"type":"object","description":"Exactly one content field must be set (\"template\" is not supported for images). All fields are accepted flat or nested inside \"options\" (flat wins).","properties":{"html":{"type":"string","description":"HTML markup to screenshot. May contain {{placeholders}} filled from \"data\"."},"markdown":{"type":"string","description":"Markdown to convert and screenshot."},"url":{"type":"string","format":"uri","description":"Public http(s) URL to screenshot; private addresses are blocked."},"data":{"description":"Values for {{placeholders}}.","anyOf":[{"type":"object"},{"type":"string"}]},"type":{"type":"string","enum":["png","jpeg"],"default":"png","description":"Image format (aliases \"format\" and \"imageType\" are mapped to this field)."},"quality":{"type":"number","default":85,"description":"JPEG quality."},"width":{"type":"number","default":1280,"description":"Viewport width in px."},"height":{"type":"number","default":800,"description":"Viewport height in px."},"deviceScaleFactor":{"type":"number","default":2},"fullPage":{"type":"boolean","default":true},"omitBackground":{"type":"boolean","default":false},"waitFor":{"description":"Wait for a selector or a fixed delay. Aliases: \"waitUntil\", \"wait\", \"delay\".","anyOf":[{"type":"string"},{"type":"number"}]},"timeout":{"description":"Render timeout in ms; minimum 1000. Alias: \"timeoutMs\".","anyOf":[{"type":"number"},{"type":"string"}]},"timeoutMs":{"description":"Alias of \"timeout\".","anyOf":[{"type":"number"},{"type":"string"}]},"javascript":{"type":"boolean"},"css":{"type":"string"},"googleFonts":{"type":"string"},"strict":{"type":"boolean","default":false,"description":"Refuse blank or placeholder-laden screenshots with 400."},"output":{"type":"string","enum":["binary","url","base64"],"default":"binary"},"filename":{"type":"string","description":"Download name; the extension is corrected to match the type, with a warning when it lied."},"expiresInMinutes":{"description":"Hosted-file TTL for output \"url\".","anyOf":[{"type":"number"},{"type":"string"}]}}}}}},"responses":{"200":{"description":"The rendered image (image/png or image/jpeg for binary output; JSON for url/base64).","content":{"image/png":{"schema":{"type":"string","format":"binary"}},"image/jpeg":{"schema":{"type":"string","format":"binary"}},"application/json":{"schema":{"$ref":"#/components/schemas/FileJson"}}}},"400":{"description":"`missing_content` — Nothing to render: send exactly one of \"html\", \"markdown\", \"url\" or \"template\".\n\n`ambiguous_content` — More than one of \"html\", \"markdown\", \"url\" or \"template\" was sent.\n\n`unknown_field` — The request contains a field the endpoint does not accept.\n\n`invalid_option` — An option value is malformed or out of range.\n\n`invalid_data` — The \"data\" field is a string but is not valid JSON.\n\n`unsupported_source` — Saved templates cannot be rendered as images.\n\n`invalid_url` — The URL field is not a valid URL.\n\n`unsupported_url_scheme` — The URL field must use http:// or https://.\n\n`private_address_blocked` — The URL points at (or resolves to) a private, loopback or link-local address; SSRF is blocked.\n\n`dns_failed` — The host in the URL could not be resolved.\n\n`unresolved_placeholders` — The template uses placeholders that \"data\" does not provide (strict mode).\n\n`invalid_placeholder_value` — A placeholder value would print as \"[object Object]\" (strict mode).\n\n`blank_document` — The document has no visible content (strict mode).\n\n`unrendered_markup` — The markup was printed as text instead of being applied (strict mode).\n\n`file_too_large` — The generated file is over the hosted-file size limit; use output \"binary\".","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"x-error-codes":[{"code":"missing_content","meaning":"Nothing to render: send exactly one of \"html\", \"markdown\", \"url\" or \"template\"."},{"code":"ambiguous_content","meaning":"More than one of \"html\", \"markdown\", \"url\" or \"template\" was sent."},{"code":"unknown_field","meaning":"The request contains a field the endpoint does not accept."},{"code":"invalid_option","meaning":"An option value is malformed or out of range."},{"code":"invalid_data","meaning":"The \"data\" field is a string but is not valid JSON."},{"code":"unsupported_source","meaning":"Saved templates cannot be rendered as images."},{"code":"invalid_url","meaning":"The URL field is not a valid URL."},{"code":"unsupported_url_scheme","meaning":"The URL field must use http:// or https://."},{"code":"private_address_blocked","meaning":"The URL points at (or resolves to) a private, loopback or link-local address; SSRF is blocked."},{"code":"dns_failed","meaning":"The host in the URL could not be resolved."},{"code":"unresolved_placeholders","meaning":"The template uses placeholders that \"data\" does not provide (strict mode)."},{"code":"invalid_placeholder_value","meaning":"A placeholder value would print as \"[object Object]\" (strict mode)."},{"code":"blank_document","meaning":"The document has no visible content (strict mode)."},{"code":"unrendered_markup","meaning":"The markup was printed as text instead of being applied (strict mode)."},{"code":"file_too_large","meaning":"The generated file is over the hosted-file size limit; use output \"binary\"."}]},"401":{"description":"`missing_api_key` — No API key was sent (Authorization: Bearer or x-api-key).\n\n`invalid_api_key` — The API key is not valid or has been revoked.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"x-error-codes":[{"code":"missing_api_key","meaning":"No API key was sent (Authorization: Bearer or x-api-key)."},{"code":"invalid_api_key","meaning":"The API key is not valid or has been revoked."}]},"402":{"description":"`plan_required` — The account has no plan, so it cannot render anything yet.\n\n`quota_exceeded` — All documents included in the monthly plan have been used.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"x-error-codes":[{"code":"plan_required","meaning":"The account has no plan, so it cannot render anything yet."},{"code":"quota_exceeded","meaning":"All documents included in the monthly plan have been used."}]},"429":{"description":"`rate_limited` — Too many requests: 120 per minute per account with a burst of 30. Retry-After says how long to wait.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"x-error-codes":[{"code":"rate_limited","meaning":"Too many requests: 120 per minute per account with a burst of 30. Retry-After says how long to wait."}]},"500":{"description":"`renderer_crashed` — The renderer stopped before the document was finished — most likely the page is too large or too complex; on an async job this happens after two attempts.\n\n`render_failed` — The renderer could not produce the document; a failed async job carries this code in its error object.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"x-error-codes":[{"code":"renderer_crashed","meaning":"The renderer stopped before the document was finished — most likely the page is too large or too complex; on an async job this happens after two attempts."},{"code":"render_failed","meaning":"The renderer could not produce the document; a failed async job carries this code in its error object."}]},"502":{"description":"`url_unreachable` — The URL could not be loaded: the host did not resolve, refused the connection or has an invalid TLS certificate.\n\n`url_http_error` — The URL was reached but answered with an HTTP error status; details.upstream_status carries it.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"x-error-codes":[{"code":"url_unreachable","meaning":"The URL could not be loaded: the host did not resolve, refused the connection or has an invalid TLS certificate."},{"code":"url_http_error","meaning":"The URL was reached but answered with an HTTP error status; details.upstream_status carries it."}]},"503":{"description":"`renderer_busy` — PDFMint is at capacity: the render queue is full. Retry in a few seconds.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"x-error-codes":[{"code":"renderer_busy","meaning":"PDFMint is at capacity: the render queue is full. Retry in a few seconds."}]},"504":{"description":"`render_timeout` — Rendering exceeded the \"timeout\" while loading or printing the document.\n\n`wait_for_timeout` — The selector passed as \"waitFor\" did not appear on the page before the timeout elapsed.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"x-error-codes":[{"code":"render_timeout","meaning":"Rendering exceeded the \"timeout\" while loading or printing the document."},{"code":"wait_for_timeout","meaning":"The selector passed as \"waitFor\" did not appear on the page before the timeout elapsed."}]}}}},"/v1/merge":{"post":{"tags":["render"],"summary":"Merge 2-50 PDFs into one","description":"Costs 1 credit. Each input is a public http(s) URL, a base64 string, or {\"base64\": \"...\"}.","security":[{"bearerAuth":[]},{"apiKeyAuth":[]}],"requestBody":{"description":"","content":{"application/json":{"schema":{"type":"object","description":"Merges 2-50 PDFs. Inputs are accepted under \"files\" (also aliased as \"urls\" or \"pdfs\"); each entry is a public http(s) URL string, a base64 string, or {\"base64\": \"...\"}.","properties":{"files":{"type":"array","minItems":2,"maxItems":50,"items":{"anyOf":[{"type":"string","format":"uri"},{"type":"string","description":"base64-encoded PDF"},{"type":"object","properties":{"base64":{"type":"string"}},"required":["base64"]}]}},"urls":{"type":"array","minItems":2,"maxItems":50,"items":{},"description":"Alias of \"files\"."},"pdfs":{"type":"array","minItems":2,"maxItems":50,"items":{},"description":"Alias of \"files\"."},"output":{"type":"string","enum":["binary","url","base64"],"default":"binary"},"filename":{"type":"string","default":"merged.pdf","description":"\".pdf\" is appended if missing."},"metadata":{"type":"object","description":"PDF metadata for the merged document."},"expiresInMinutes":{"description":"Hosted-file TTL for output \"url\".","anyOf":[{"type":"number"},{"type":"string"}]},"strict":{"type":"boolean","default":false,"description":"Refuse a merge whose pages would be empty with 400."},"options":{"type":"object","description":"Accepted wrapper; its keys are folded into the body (flat keys win)."}}}}}},"responses":{"200":{"description":"The merged PDF (binary) or JSON metadata (url/base64).","content":{"application/pdf":{"schema":{"type":"string","format":"binary"}},"application/json":{"schema":{"$ref":"#/components/schemas/FileJson"}}}},"400":{"description":"`invalid_input` — \"files\" must be an array of at least two PDF URLs or {\"base64\": ...} entries.\n\n`too_many_files` — More than 50 inputs were sent to one merge.\n\n`unknown_field` — The request contains a field the endpoint does not accept.\n\n`invalid_option` — An option value is malformed or out of range.\n\n`download_failed` — An input URL could not be downloaded for the merge.\n\n`invalid_pdf` — One of the merge inputs is not a readable PDF.\n\n`blank_document` — The document has no visible content (strict mode).\n\n`file_too_large` — The generated file is over the hosted-file size limit; use output \"binary\".","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"x-error-codes":[{"code":"invalid_input","meaning":"\"files\" must be an array of at least two PDF URLs or {\"base64\": ...} entries."},{"code":"too_many_files","meaning":"More than 50 inputs were sent to one merge."},{"code":"unknown_field","meaning":"The request contains a field the endpoint does not accept."},{"code":"invalid_option","meaning":"An option value is malformed or out of range."},{"code":"download_failed","meaning":"An input URL could not be downloaded for the merge."},{"code":"invalid_pdf","meaning":"One of the merge inputs is not a readable PDF."},{"code":"blank_document","meaning":"The document has no visible content (strict mode)."},{"code":"file_too_large","meaning":"The generated file is over the hosted-file size limit; use output \"binary\"."}]},"401":{"description":"`missing_api_key` — No API key was sent (Authorization: Bearer or x-api-key).\n\n`invalid_api_key` — The API key is not valid or has been revoked.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"x-error-codes":[{"code":"missing_api_key","meaning":"No API key was sent (Authorization: Bearer or x-api-key)."},{"code":"invalid_api_key","meaning":"The API key is not valid or has been revoked."}]},"402":{"description":"`plan_required` — The account has no plan, so it cannot render anything yet.\n\n`quota_exceeded` — All documents included in the monthly plan have been used.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"x-error-codes":[{"code":"plan_required","meaning":"The account has no plan, so it cannot render anything yet."},{"code":"quota_exceeded","meaning":"All documents included in the monthly plan have been used."}]},"429":{"description":"`rate_limited` — Too many requests: 120 per minute per account with a burst of 30. Retry-After says how long to wait.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"x-error-codes":[{"code":"rate_limited","meaning":"Too many requests: 120 per minute per account with a burst of 30. Retry-After says how long to wait."}]}}}},"/v1/jobs/{id}":{"get":{"tags":["jobs"],"summary":"Poll an asynchronous render job","description":"Returns the job’s status; when succeeded it carries the hosted result fields (url, filename, pages, size, expires_in_minutes, duration_ms). Finished jobs are deleted after a week.","security":[{"bearerAuth":[]},{"apiKeyAuth":[]}],"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"},"description":"The job_id returned by an async request."}],"responses":{"200":{"description":"The job. Its \"error\" object, when present, carries job_cancelled, render_failed, renderer_crashed — or any render-time code such as html_too_large.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Job"}}},"x-error-codes":[{"code":"job_cancelled","meaning":"The job was cancelled before it finished."},{"code":"render_failed","meaning":"The renderer could not produce the document; a failed async job carries this code in its error object."},{"code":"renderer_crashed","meaning":"The renderer stopped before the document was finished — most likely the page is too large or too complex; on an async job this happens after two attempts."},{"code":"account_gone","meaning":"The account that queued this job no longer exists."}]},"401":{"description":"`missing_api_key` — No API key was sent (Authorization: Bearer or x-api-key).\n\n`invalid_api_key` — The API key is not valid or has been revoked.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"x-error-codes":[{"code":"missing_api_key","meaning":"No API key was sent (Authorization: Bearer or x-api-key)."},{"code":"invalid_api_key","meaning":"The API key is not valid or has been revoked."}]},"404":{"description":"`job_not_found` — No job with that ID exists on this account.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"x-error-codes":[{"code":"job_not_found","meaning":"No job with that ID exists on this account."}]},"429":{"description":"`rate_limited` — Too many requests: 120 per minute per account with a burst of 30. Retry-After says how long to wait.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"x-error-codes":[{"code":"rate_limited","meaning":"Too many requests: 120 per minute per account with a burst of 30. Retry-After says how long to wait."}]}}},"delete":{"tags":["jobs"],"summary":"Cancel a queued or running job","description":"Marks the job cancelled and refunds the credit it consumed. The result of an already-finished job cannot be cancelled.","security":[{"bearerAuth":[]},{"apiKeyAuth":[]}],"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"","content":{"application/json":{"schema":{"type":"object","properties":{"job_id":{"type":"string"},"status":{"type":"string","enum":["cancelled"]}},"required":["job_id","status"]}}}},"401":{"description":"`missing_api_key` — No API key was sent (Authorization: Bearer or x-api-key).\n\n`invalid_api_key` — The API key is not valid or has been revoked.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"x-error-codes":[{"code":"missing_api_key","meaning":"No API key was sent (Authorization: Bearer or x-api-key)."},{"code":"invalid_api_key","meaning":"The API key is not valid or has been revoked."}]},"404":{"description":"`job_not_found` — No job with that ID exists on this account.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"x-error-codes":[{"code":"job_not_found","meaning":"No job with that ID exists on this account."}]},"409":{"description":"`job_already_finished` — The job is already in a final state and cannot be cancelled.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"x-error-codes":[{"code":"job_already_finished","meaning":"The job is already in a final state and cannot be cancelled."}]},"429":{"description":"`rate_limited` — Too many requests: 120 per minute per account with a burst of 30. Retry-After says how long to wait.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"x-error-codes":[{"code":"rate_limited","meaning":"Too many requests: 120 per minute per account with a burst of 30. Retry-After says how long to wait."}]}}}},"/v1/templates":{"get":{"tags":["templates"],"summary":"List stored templates","security":[{"bearerAuth":[]},{"apiKeyAuth":[]}],"responses":{"200":{"description":"","content":{"application/json":{"schema":{"type":"object","properties":{"templates":{"type":"array","items":{"type":"object","properties":{"name":{"type":"string"},"options":{"type":"object"},"html_bytes":{"type":"integer"},"updated_at":{"type":"string","format":"date-time"}},"required":["name","options","html_bytes","updated_at"]}}},"required":["templates"]}}}},"401":{"description":"`missing_api_key` — No API key was sent (Authorization: Bearer or x-api-key).\n\n`invalid_api_key` — The API key is not valid or has been revoked.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"x-error-codes":[{"code":"missing_api_key","meaning":"No API key was sent (Authorization: Bearer or x-api-key)."},{"code":"invalid_api_key","meaning":"The API key is not valid or has been revoked."}]},"429":{"description":"`rate_limited` — Too many requests: 120 per minute per account with a burst of 30. Retry-After says how long to wait.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"x-error-codes":[{"code":"rate_limited","meaning":"Too many requests: 120 per minute per account with a burst of 30. Retry-After says how long to wait."}]}}}},"/v1/templates/{name}":{"put":{"tags":["templates"],"summary":"Create or replace a stored template","description":"Names are 1-64 characters of letters, digits, spaces, dot, dash or underscore; the body is up to 2 MB of HTML with {{placeholders}}. Optional \"options\" are validated immediately. The response includes the placeholders the template uses and a ready-to-paste \"data\" shape.","security":[{"bearerAuth":[]},{"apiKeyAuth":[]}],"parameters":[{"name":"name","in":"path","required":true,"schema":{"type":"string"}}],"requestBody":{"description":"","content":{"application/json":{"schema":{"type":"object","required":["html"],"properties":{"html":{"type":"string","description":"Template markup with {{placeholders}}, {{#sections}} and {{{raw}}} markers."},"options":{"type":"object","description":"PDF page options (same names as PdfOptions, without the content fields); unknown keys are refused."}}}}}},"responses":{"200":{"description":"","content":{"application/json":{"schema":{"type":"object","properties":{"template":{"type":"object","properties":{"name":{"type":"string"},"updated_at":{"type":"string","format":"date-time"}},"required":["name","updated_at"]},"placeholders":{"type":"array","items":{"type":"object","properties":{"name":{"type":"string"},"kind":{"type":"string","enum":["scalar","raw","section","inverted"]},"scope":{"type":"string"}},"required":["name","kind"]}},"usage":{"type":"object","properties":{"template":{"type":"string"},"data":{"type":"object","description":"A minimal \"data\" object of the right shape for this template."}},"required":["template","data"]}},"required":["template","placeholders","usage"]}}}},"400":{"description":"`invalid_template_name` — The template name is not 1-64 characters of letters, digits, spaces, dot, dash or underscore.\n\n`missing_content` — Nothing to render: send exactly one of \"html\", \"markdown\", \"url\" or \"template\".\n\n`template_too_large` — The template body is over the 2 MB limit.\n\n`unknown_field` — The request contains a field the endpoint does not accept.\n\n`invalid_option` — An option value is malformed or out of range.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"x-error-codes":[{"code":"invalid_template_name","meaning":"The template name is not 1-64 characters of letters, digits, spaces, dot, dash or underscore."},{"code":"missing_content","meaning":"Nothing to render: send exactly one of \"html\", \"markdown\", \"url\" or \"template\"."},{"code":"template_too_large","meaning":"The template body is over the 2 MB limit."},{"code":"unknown_field","meaning":"The request contains a field the endpoint does not accept."},{"code":"invalid_option","meaning":"An option value is malformed or out of range."}]},"401":{"description":"`missing_api_key` — No API key was sent (Authorization: Bearer or x-api-key).\n\n`invalid_api_key` — The API key is not valid or has been revoked.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"x-error-codes":[{"code":"missing_api_key","meaning":"No API key was sent (Authorization: Bearer or x-api-key)."},{"code":"invalid_api_key","meaning":"The API key is not valid or has been revoked."}]},"429":{"description":"`rate_limited` — Too many requests: 120 per minute per account with a burst of 30. Retry-After says how long to wait.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"x-error-codes":[{"code":"rate_limited","meaning":"Too many requests: 120 per minute per account with a burst of 30. Retry-After says how long to wait."}]}}},"get":{"tags":["templates"],"summary":"Fetch a stored template","security":[{"bearerAuth":[]},{"apiKeyAuth":[]}],"parameters":[{"name":"name","in":"path","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"","content":{"application/json":{"schema":{"type":"object","properties":{"name":{"type":"string"},"html":{"type":"string"},"options":{"type":"object"},"updated_at":{"type":"string","format":"date-time"},"placeholders":{"type":"array","items":{"type":"object"}},"usage":{"type":"object","properties":{"template":{"type":"string"},"data":{"type":"object"}}}},"required":["name","html","options","updated_at","placeholders","usage"]}}}},"400":{"description":"`template_not_found` — No stored template with that name exists on this account.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"x-error-codes":[{"code":"template_not_found","meaning":"No stored template with that name exists on this account."}]},"401":{"description":"`missing_api_key` — No API key was sent (Authorization: Bearer or x-api-key).\n\n`invalid_api_key` — The API key is not valid or has been revoked.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"x-error-codes":[{"code":"missing_api_key","meaning":"No API key was sent (Authorization: Bearer or x-api-key)."},{"code":"invalid_api_key","meaning":"The API key is not valid or has been revoked."}]},"429":{"description":"`rate_limited` — Too many requests: 120 per minute per account with a burst of 30. Retry-After says how long to wait.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"x-error-codes":[{"code":"rate_limited","meaning":"Too many requests: 120 per minute per account with a burst of 30. Retry-After says how long to wait."}]}}},"delete":{"tags":["templates"],"summary":"Delete a stored template","security":[{"bearerAuth":[]},{"apiKeyAuth":[]}],"parameters":[{"name":"name","in":"path","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"","content":{"application/json":{"schema":{"type":"object","properties":{"deleted":{"type":"string"}},"required":["deleted"]}}}},"400":{"description":"`template_not_found` — No stored template with that name exists on this account.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"x-error-codes":[{"code":"template_not_found","meaning":"No stored template with that name exists on this account."}]},"401":{"description":"`missing_api_key` — No API key was sent (Authorization: Bearer or x-api-key).\n\n`invalid_api_key` — The API key is not valid or has been revoked.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"x-error-codes":[{"code":"missing_api_key","meaning":"No API key was sent (Authorization: Bearer or x-api-key)."},{"code":"invalid_api_key","meaning":"The API key is not valid or has been revoked."}]},"429":{"description":"`rate_limited` — Too many requests: 120 per minute per account with a burst of 30. Retry-After says how long to wait.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"x-error-codes":[{"code":"rate_limited","meaning":"Too many requests: 120 per minute per account with a burst of 30. Retry-After says how long to wait."}]}}}},"/v1/keys":{"post":{"tags":["keys"],"summary":"Issue a new API key","description":"Returns the full key once; it cannot be read back afterwards. Optional \"label\" is truncated to 40 characters.","security":[{"bearerAuth":[]},{"apiKeyAuth":[]}],"requestBody":{"description":"","content":{"application/json":{"schema":{"type":"object","properties":{"label":{"type":"string","default":"default","maxLength":40}}}}}},"responses":{"200":{"description":"","content":{"application/json":{"schema":{"type":"object","properties":{"api_key":{"type":"string","description":"Starts with pm_live_."}},"required":["api_key"]}}}},"401":{"description":"`missing_api_key` — No API key was sent (Authorization: Bearer or x-api-key).\n\n`invalid_api_key` — The API key is not valid or has been revoked.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"x-error-codes":[{"code":"missing_api_key","meaning":"No API key was sent (Authorization: Bearer or x-api-key)."},{"code":"invalid_api_key","meaning":"The API key is not valid or has been revoked."}]},"429":{"description":"`rate_limited` — Too many requests: 120 per minute per account with a burst of 30. Retry-After says how long to wait.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"x-error-codes":[{"code":"rate_limited","meaning":"Too many requests: 120 per minute per account with a burst of 30. Retry-After says how long to wait."}]}}},"get":{"tags":["keys"],"summary":"List active API keys","security":[{"bearerAuth":[]},{"apiKeyAuth":[]}],"responses":{"200":{"description":"","content":{"application/json":{"schema":{"type":"object","properties":{"keys":{"type":"array","items":{"type":"object","properties":{"key_prefix":{"type":"string"},"label":{"type":"string"},"created_at":{"type":"string","format":"date-time"},"last_used_at":{"type":["string","null"],"format":"date-time"}},"required":["key_prefix","label","created_at"]}}},"required":["keys"]}}}},"401":{"description":"`missing_api_key` — No API key was sent (Authorization: Bearer or x-api-key).\n\n`invalid_api_key` — The API key is not valid or has been revoked.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"x-error-codes":[{"code":"missing_api_key","meaning":"No API key was sent (Authorization: Bearer or x-api-key)."},{"code":"invalid_api_key","meaning":"The API key is not valid or has been revoked."}]},"429":{"description":"`rate_limited` — Too many requests: 120 per minute per account with a burst of 30. Retry-After says how long to wait.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"x-error-codes":[{"code":"rate_limited","meaning":"Too many requests: 120 per minute per account with a burst of 30. Retry-After says how long to wait."}]}}}},"/v1/keys/{prefix}":{"delete":{"tags":["keys"],"summary":"Revoke an API key by its prefix","description":"Takes effect on the next request. Refuses to revoke the account’s last remaining key.","security":[{"bearerAuth":[]},{"apiKeyAuth":[]}],"parameters":[{"name":"prefix","in":"path","required":true,"schema":{"type":"string"},"description":"The key_prefix from GET /v1/keys."}],"responses":{"200":{"description":"","content":{"application/json":{"schema":{"type":"object","properties":{"revoked":{"type":"string"}},"required":["revoked"]}}}},"401":{"description":"`missing_api_key` — No API key was sent (Authorization: Bearer or x-api-key).\n\n`invalid_api_key` — The API key is not valid or has been revoked.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"x-error-codes":[{"code":"missing_api_key","meaning":"No API key was sent (Authorization: Bearer or x-api-key)."},{"code":"invalid_api_key","meaning":"The API key is not valid or has been revoked."}]},"404":{"description":"`key_not_found` — No active key on this account starts with the given prefix.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"x-error-codes":[{"code":"key_not_found","meaning":"No active key on this account starts with the given prefix."}]},"409":{"description":"`last_key` — That is the only key on the account, so revoking it would lock the account out.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"x-error-codes":[{"code":"last_key","meaning":"That is the only key on the account, so revoking it would lock the account out."}]},"429":{"description":"`rate_limited` — Too many requests: 120 per minute per account with a burst of 30. Retry-After says how long to wait.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"x-error-codes":[{"code":"rate_limited","meaning":"Too many requests: 120 per minute per account with a burst of 30. Retry-After says how long to wait."}]}}}},"/v1/forgot-password":{"post":{"tags":["recovery"],"summary":"Request a password-reset link","description":"Always answers the same whether or not the account exists. Rate-limited to 20 attempts per hour per client IP address.","security":[],"requestBody":{"description":"","content":{"application/json":{"schema":{"type":"object","required":["email"],"properties":{"email":{"type":"string","format":"email"}}}}}},"responses":{"202":{"description":"","content":{"application/json":{"schema":{"type":"object","properties":{"message":{"type":"string"}},"required":["message"]}}}},"400":{"description":"`invalid_email` — the address is not valid.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"x-error-codes":[{"code":"invalid_email","meaning":"The email address is not valid."}]},"429":{"description":"`rate_limited` — more than 20 recovery attempts in one hour.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"x-error-codes":[{"code":"rate_limited","meaning":"Too many requests: 120 per minute per account with a burst of 30. Retry-After says how long to wait."}]},"503":{"description":"`recovery_unavailable` — email delivery is temporarily down.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"x-error-codes":[{"code":"recovery_unavailable","meaning":"Email delivery is temporarily unavailable."}]}}}},"/v1/reset-password":{"post":{"tags":["recovery"],"summary":"Complete a password reset","description":"Consumes the single-use token from the reset email (valid 30 minutes). Password: at least 8 characters, at most 72 UTF-8 bytes.","security":[],"requestBody":{"description":"","content":{"application/json":{"schema":{"type":"object","required":["token","password"],"properties":{"token":{"type":"string"},"password":{"type":"string","minLength":8}}}}}},"responses":{"200":{"description":"","content":{"application/json":{"schema":{"type":"object","properties":{"message":{"type":"string"}},"required":["message"]}}}},"400":{"description":"`invalid_reset_token` or `invalid_password`.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"x-error-codes":[{"code":"invalid_reset_token","meaning":"The reset link is invalid or has expired."},{"code":"invalid_password","meaning":"The new password is too short or over 72 UTF-8 bytes."}]},"429":{"description":"`rate_limited` — more than 20 recovery attempts in one hour.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"x-error-codes":[{"code":"rate_limited","meaning":"Too many requests: 120 per minute per account with a burst of 30. Retry-After says how long to wait."}]}}}}},"x-app-level-errors":[{"code":"unknown_endpoint","status":404,"meaning":"The path does not exist on this API; the /v1/* 404 fall-through answers this."},{"code":"invalid_json","status":400,"meaning":"The request body is not valid JSON."},{"code":"request_too_large","status":413,"meaning":"The request body is over the size limit the server accepts."},{"code":"internal_error","status":500,"meaning":"Something failed on the server while handling the request."}],"components":{"securitySchemes":{"bearerAuth":{"type":"http","scheme":"bearer","description":"Authorization: Bearer pm_live_..."},"apiKeyAuth":{"type":"apiKey","in":"header","name":"x-api-key","description":"x-api-key: pm_live_..."}},"responses":{"AppLevelError":{"description":"The JSON error body the app-level handlers in the server answer with, on any request: the /v1/* 404 fall-through (`unknown_endpoint`), an oversized request body (`request_too_large`, 413), a body that is not valid JSON (`invalid_json`, 400) or an unhandled server fault (`internal_error`, 500). These codes are not tied to one operation; the full inventory with the status each handler answers with is in the top-level `x-app-level-errors` section.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}},"schemas":{"Error":{"type":"object","description":"The error wire shape of src/errors.js: { error: { code, message } }, with optional hint, docs, details and request_id.","properties":{"error":{"type":"object","properties":{"code":{"type":"string","description":"A stable machine-readable code, e.g. unknown_field."},"message":{"type":"string","description":"A one-line human message."},"hint":{"type":"string","description":"A concrete suggestion of what to change. Present when the error has one."},"docs":{"type":"string","description":"Absolute URL of the relevant docs anchor. Present when the error has one."},"details":{"type":"object","description":"Extra machine-readable facts, e.g. { unknown: [...] }. Present when the error has them."},"request_id":{"type":"string","description":"The X-Request-Id of the request; quote it when reporting."}},"required":["code","message"]}},"required":["error"]},"FileJson":{"type":"object","properties":{"filename":{"type":"string"},"pages":{"type":["integer","null"],"description":"Page count (absent/null when the PDF is encrypted)."},"size":{"type":"integer","description":"File size in bytes."},"url":{"type":"string","format":"uri","description":"Temporary hosted-file link (output \"url\", or the finished async job)."},"expires_in_minutes":{"type":"integer","description":"How long the hosted link stays valid."},"base64":{"type":"string","description":"The file bytes, base64 (output \"base64\" only)."},"duration_ms":{"type":"integer"},"credits_remaining":{"type":"integer"},"warnings":{"type":"array","items":{"type":"string"}},"debug":{"type":"object","description":"Only on POST /v1/pdf with \"debug\": true and output url or base64: what the renderer saw while producing the page.","properties":{"renderedHtml":{"type":["string","null"],"description":"The HTML of the page after placeholder filling; null if it could not be read back."},"finalUrl":{"type":"string","format":"uri","description":"The page URL after redirects; \"about:blank\" when the source was html or markdown."},"pageErrors":{"type":"array","items":{"type":"string"},"description":"Up to 5 page errors (uncaught exceptions) the page produced; the same list is sent in the X-PDFMint-Page-Errors header."}},"required":["renderedHtml","finalUrl","pageErrors"]}}},"Job":{"type":"object","properties":{"job_id":{"type":"string","description":"Job ID, \"job_\" followed by base64url characters."},"kind":{"type":"string","enum":["pdf"]},"status":{"type":"string","enum":["queued","running","succeeded","failed","cancelled"]},"created_at":{"type":"string","format":"date-time"},"started_at":{"type":["string","null"],"format":"date-time"},"finished_at":{"type":["string","null"],"format":"date-time"},"attempts":{"type":"integer"},"filename":{"type":"string"},"pages":{"type":"integer"},"size":{"type":"integer"},"url":{"type":"string","format":"uri","description":"Present once succeeded: the hosted result file."},"expires_in_minutes":{"type":"integer"},"duration_ms":{"type":"integer"},"error":{"type":"object","description":"Present when the job failed or was cancelled.","properties":{"code":{"type":"string","description":"The failure code. Any render-time code can land here (e.g. html_too_large, render_timeout, renderer_busy), plus job_cancelled for a cancelled job; src/jobs.js stores e.code || \"render_failed\"."},"message":{"type":"string"},"hint":{"type":"string"}},"required":["code","message"]}},"required":["job_id","kind","status","created_at","attempts"]}}}}