# PrintFriendly API (full reference for AI agents) > PrintFriendly turns a web page, a piece of HTML or a piece of Markdown into a clean PDF, a Word (docx) file, Markdown, HTML, JSON or plain text. Ads, navigation and clutter are removed from web pages. One HTTP call, one answer. This file is the whole API in plain text, written for AI agents and the people who build them. The same API is described in machine-readable form at https://www.printfriendly.com/openapi.json (OpenAPI 3.1) and for people at https://www.printfriendly.com/api/instructions/v2. ## What PrintFriendly does - Reads a web page the way a person would and keeps only the article: title, author, date, text, images, tables. No ads, menus or pop-ups. - Turns that article, or your own HTML or Markdown, into the format you ask for. - Hosts PDF and Word files for 48 hours and gives you a link. Markdown, HTML, JSON and text come back in the response itself. Typical uses: give an agent the clean text of a page to read or summarize, save a page or a generated report as a PDF, turn Markdown notes into a Word document. ## Get an API key 1. Open https://www.printfriendly.com/me/api-key (sign in or sign up; it is free). 2. Copy the key. The free plan includes 100 documents a month, no card needed. Paid plans include more: see https://www.printfriendly.com/api#pricing. ## Authentication Send the key in a header. Any of these works: Authorization: Bearer YOUR_API_KEY X-API-Key: YOUR_API_KEY The key can also go in the query string as `?api_key=YOUR_API_KEY`, but URLs get logged, so prefer a header. HTTP Basic (key as the username, empty password) also works; on the older /v2/pdf/create style endpoints it returns the raw file instead of JSON. Requests must use HTTPS. The base URL is https://api.printfriendly.com. ## The convert endpoint POST https://api.printfriendly.com/v2/convert (`GET` works too, with the same parameters in the query string.) The body can be JSON, a urlencoded form or multipart. By default the call is synchronous and answers within 27 seconds. For slow pages and large documents, add `"async": true` to run it in the background (see "Background jobs" below). ### Input: send exactly one of url, html or markdown - `url`: a web page address. We fetch it and keep the article. (`page_url` is accepted as an alias.) - `html`: HTML to convert. It is treated as the article body; scripts and anything unsafe are removed. Up to 2 MB of markup (embedded data: images do not count, 50 MB in all). - `markdown`: Markdown to convert, up to 1 MB. GitHub-flavoured: headings, lists, tables, code blocks. ### Options - `output`: one of `pdf` (default), `docx` (Word), `markdown`, `html`, `json`, `text`. - `title`: heading printed above `html` or `markdown`. Ignored for `url`, which uses the page's own title. - `source_url`: with `html` only. Where the HTML came from; shown under the title and used for relative links. - `page_size`: `letter` (default) or `a4`. For `pdf` and `docx` (Word). - `css_url`: address of a stylesheet to apply. For `pdf` and `html`. (`custom_css_url` is accepted as an alias.) - `header_text`: a line of text shown above the title. For `pdf` and `html`. - `header_image_url`: an image shown above the title, such as a logo. For `pdf` and `html`. - `lang`: language of the content, such as `en` or `pt-BR`. Defaults to the page's own. - `dir`: `ltr` or `rtl`. Defaults to the page's own. - `include_links`: `true` keeps link and image addresses in `markdown` and `text` output. Default `false`: links keep their words and lose their addresses, which makes the output far shorter. - `async`: `true` runs the conversion in the background and answers `202` with a job id. Default `false`. See "Background jobs". - `mode`: `"async"` or `"sync"`, another way to write `async`. If you send both they must agree. - `webhook_url`: with `async` only. An https address on a public host that we POST the finished job to. ### Output formats - `pdf`: a PDF file. You get `file_url`. - `docx`: a Word file. You get `file_url`. For a `url` input the page's pictures are fetched and included: up to 20 of them, 5 MB each, 20 MB in all, within 10 seconds. Large pictures are scaled down to fit a page. A picture that cannot be fetched is left out, never the document. For `html` and `markdown` input only images embedded as data: addresses are included. - `markdown`: the article as Markdown, in `content`. Starts with the title as a heading. Tables stay tables. - `html`: a standalone page with PrintFriendly's print styling, in `content`. - `json`: the article in pieces, in `document`: `title`, `url`, `site`, `author`, `date` (ISO 8601 when known), `excerpt`, `image`, `word_count`, `content_html`, `content_markdown`. - `text`: plain text, in `content`, with the Markdown syntax removed. ### What comes back A good request answers `200`: { "status": "success", "output": "pdf", "file_url": "https://storage.printfriendly.com/data-production/pdf_1760380000000_a1b2c3Letter.pdf", "expires_at": "2026-10-15T12:00:00.000Z", "size_bytes": 184233, "document": { "title": "Eclipse", "url": "https://en.wikipedia.org/wiki/Eclipse", "site": "en.wikipedia.org", "word_count": 6120 }, "usage": { "used_this_period": 3, "included": 100, "plan": "free" } } - `pdf` and `docx` (Word) add `file_url`, `expires_at` and `size_bytes`. The link works for 48 hours. - `markdown`, `html` and `text` add `content`. - `json` puts the whole article in `document`. - `usage` says how many documents the key has used this period and how many it includes. Only a successful conversion counts toward the plan: one per request, whatever the format. Failed requests are free. ### Three examples Web page to PDF: curl -X POST https://api.printfriendly.com/v2/convert \ -H "Authorization: Bearer YOUR_API_KEY" \ -H "Content-Type: application/json" \ -d '{"url": "https://en.wikipedia.org/wiki/Eclipse", "output": "pdf"}' Web page to Markdown (the answer's `content` is the article): curl -X POST https://api.printfriendly.com/v2/convert \ -H "Authorization: Bearer YOUR_API_KEY" \ -H "Content-Type: application/json" \ -d '{"url": "https://en.wikipedia.org/wiki/Eclipse", "output": "markdown"}' Markdown to Word: curl -X POST https://api.printfriendly.com/v2/convert \ -H "Authorization: Bearer YOUR_API_KEY" \ -H "Content-Type: application/json" \ -d '{"markdown": "# Notes\n\n- First point\n- Second point", "title": "Meeting notes", "output": "docx"}' For the article as structured data, use `"output": "json"` and read `document.title`, `document.word_count` and `document.content_markdown`. ## Check a key and read its usage GET https://api.printfriendly.com/v2/usage Free: it does not use a document, is never counted toward your plan and records no usage event. Use it to check that a key works, for example when connecting an app. curl https://api.printfriendly.com/v2/usage \ -H "Authorization: Bearer YOUR_API_KEY" { "status": "success", "usage": { "plan": "free", "used_this_period": 42, "included": 100, "remaining": 58 } } - `used_this_period` is what the key has used so far, not counting this request. A free key's period is the calendar month in UTC; a paid key's is its billing period. - `remaining` is how many of the plan's included documents are left, never below 0. - It answers `200` even when the key's allowance is used up. A free key that has used its 100 documents, and a paid key at its quota or its overage cap, get `remaining: 0`, where `/v2/convert` answers `402`. - A missing, unknown or inactive key gets the same `401` errors as any other call. The per-minute request limit applies. ## Background jobs Add `"async": true` (or `"mode": "async"`) to a convert request and it is queued instead of run while you wait. Use it for slow pages, large documents, or when your own request should not have to wait. The input, options and outputs are the same. Starting a job answers `202` straight away. Nothing is converted or counted yet: { "status": "queued", "job_id": "5b1f6a52-0c0e-4d8e-9d8f-3f0a6a7d1c21", "status_url": "https://api.printfriendly.com/v2/jobs/5b1f6a52-0c0e-4d8e-9d8f-3f0a6a7d1c21", "expires_at": "2026-10-15T12:00:00.000Z" } Read the job with `GET https://api.printfriendly.com/v2/jobs/{id}` and the same key. It has a `status`: - `queued`: accepted, waiting for a worker. - `processing`: a worker is converting it. - `succeeded`: done. `result` is exactly the body a normal `200` answer carries, `usage` included. `finished_at` is set. - `failed`: it did not work. `error` has the same `code`, `message` and `retryable` a failed request carries. `finished_at` is set. A failed job costs nothing. `queued` and `processing` are not final. Look again in a couple of seconds: the answer carries a `Retry-After` header while a job is unfinished. `succeeded` and `failed` never change. { "status": "succeeded", "job_id": "5b1f6a52-0c0e-4d8e-9d8f-3f0a6a7d1c21", "created_at": "2026-10-13T12:00:00.000Z", "finished_at": "2026-10-13T12:00:07.412Z", "result": { "status": "success", "output": "docx", "file_url": "https://storage.printfriendly.com/...", "expires_at": "...", "size_bytes": 184233, "document": { "title": "Eclipse" }, "usage": { "used_this_period": 3, "included": 100, "plan": "free" } } } Start a job and check on it: curl -X POST https://api.printfriendly.com/v2/convert \ -H "Authorization: Bearer YOUR_API_KEY" \ -H "Content-Type: application/json" \ -d '{"url": "https://en.wikipedia.org/wiki/Eclipse", "output": "docx", "async": true}' curl https://api.printfriendly.com/v2/jobs/JOB_ID \ -H "Authorization: Bearer YOUR_API_KEY" Rules: - A job can only be read by the key that started it. For any other key, and once 48 hours have passed, the answer is `404` with `error.code` `job_not_found`. - A job counts toward your plan once, when it succeeds, like a normal request. The free plan's monthly limit and the per-minute request limit are checked when you start the job. The free limit is checked again when the job runs, so jobs queued together cannot take a free key past its month. - A key can have 3 unfinished jobs on the free plan and 20 on paid plans. Past that, starting another answers `429` with `Retry-After` until one finishes. - Checking a job has its own limit, 120 a minute on the free plan and 600 on paid plans, separate from the limit on starting work. - A job has up to 55 seconds to convert. If the server running it is replaced, it is picked up again, so it is not lost. If it still cannot finish, it fails with `retryable` set to `true`. ### Webhooks Add `"webhook_url"` to an async request and we POST the finished job to it, so you do not have to keep checking. It must be an `https` address on a public host. An address that points at a private or internal network is refused with `400`. - The body is JSON: the same object `GET /v2/jobs/{id}` returns, with `status` `succeeded` or `failed`. - The `X-PrintFriendly-Signature` header is `sha256=` followed by the hex HMAC-SHA256 of the raw request body, keyed with your API key. Compute it over the exact bytes you received, compare in constant time, and ignore a request that does not match. `X-PrintFriendly-Job-Id` repeats the job id. - Answer with any `2xx`. Anything else, or no answer within 10 seconds, is tried again three times, after 2, 10 and 60 seconds. Redirects are not followed. - Delivery is best effort and never delays the job. The result is saved first and stays at `status_url` for 48 hours, so if a webhook does not arrive, read the job. A retry can arrive after a slow success: use `job_id` to ignore repeats. Checking the signature in Python: import hashlib import hmac def is_from_printfriendly(raw_body: bytes, signature_header: str, api_key: str) -> bool: expected = "sha256=" + hmac.new(api_key.encode(), raw_body, hashlib.sha256).hexdigest() return hmac.compare_digest(expected, signature_header or "") ## Errors A failed request answers with `status` set to `failed` and an `error` object: { "status": "failed", "message": "Print technology timed-out while trying to connect to this page", "error": { "code": "fetch_failed", "message": "Print technology timed-out while trying to connect to this page", "retryable": true } } Branch on `error.code`, not on the message. `error.retryable` says whether sending the same request again can work. | code | HTTP | retry? | meaning | |-----------------------|-----------|--------|---------| | invalid_parameters | 400 | no | A parameter is missing, malformed or not allowed. `error.errors` names each one. | | html_too_large | 400, 413, 422 | no | The `html` has more than 2 MB of markup or is over 50 MB, or a fetched page was too big to process. | | api_key_required | 401 | no | No key was sent. | | invalid_api_key | 401 | no | The key is not known. | | subscription_inactive | 401 | no | The plan behind the key has ended. | | limit_exceeded | 401 | no | The key has reached the limit set on it. | | quota_exceeded | 402 | no | The free plan has no documents left. `error.upgrade_url` says where to upgrade. | | rate_limited | 429 | yes | Too many requests, or (with `async`) too many unfinished jobs. Wait `Retry-After` seconds. | | fetch_failed | 422 | yes | The page could not be loaded: down, slow, or blocking us. | | parse_failed | 422 | no | The page loaded but no article could be found in it. | | homepage_url | 422 | no | The address is a site's front page, not an article. Send a link to one story. | | content_blocked | 422 | no | Returned only when content checks are enabled for the key's plan. The page failed the same content checks printfriendly.com applies (https://www.printfriendly.com/acceptable-use-policy). The same page fails every time, so do not retry it. | | render_timeout | 422 | yes | The conversion did not finish in 27 seconds. | | render_failed | 422 | yes | The conversion failed on our side. | | job_not_found | 404 | no | `GET /v2/jobs/{id}` found no job for that id under this key: wrong id, another key's job, or older than 48 hours. | ## Limits - Free plan: 100 documents a month. PDF output carries the line "Made with the PrintFriendly API (printfriendly.com/api)" at the foot of every page, and HTML output carries it once at the end. Markdown, JSON, text and Word have none. - Paid plans: more documents, no footer line. See https://www.printfriendly.com/api#pricing. - Acceptable use: API use is governed by the Acceptable Use Policy (https://www.printfriendly.com/acceptable-use-policy), and keys used in breach of it are revoked. - Request rate: 10 requests a minute on the free plan, 120 a minute on paid plans. Over that you get `rate_limited` and a `Retry-After` header. - Size: `html` up to 2 MB of markup (50 MB with embedded images), `markdown` up to 1 MB. - Time: a synchronous call answers within 27 seconds, or with `render_timeout`. An async job has up to 55 seconds. - Background jobs: 3 unfinished jobs per key on the free plan, 20 on paid plans. Jobs and their results are kept for 48 hours. - Files: `pdf` and `docx` (Word) links work for 48 hours. ## The older endpoints POST /v2/pdf/create, /v2/html/create and /v2/text/create still work exactly as before. They take `page_url` or `html` and return `{"status": "success", "file_url": "..."}`. New work should use /v2/convert. ## MCP server for agents If your agent speaks MCP, it can call PrintFriendly as tools instead of building HTTP requests itself. - URL: https://mcp.printfriendly.com (streamable HTTP, JSON-RPC) - Auth: `Authorization: Bearer YOUR_API_KEY`. It is the same key, plan and limits as the REST API. Each successful tool call counts as one document; a failed call does not count. - `read_page`: a web page as clean Markdown plus its title, author, date and word count. `url` is required. `include_links` (default `false`) keeps link addresses. `max_chars` (default 60000) cuts a long page and the answer says when it did. - `create_document`: makes a `pdf`, `docx` (Word), `markdown`, `html`, `json` or `text` (plain text) document from a `url`, `html` or `markdown`. `output` is required. PDF and Word come back as a download link that works for 48 hours. Add it to Claude Code with one line: claude mcp add --transport http printfriendly https://mcp.printfriendly.com --header "Authorization: Bearer YOUR_API_KEY" Setup for Cursor, VS Code and other clients: https://www.printfriendly.com/help/api-mcp ## For people, not code - Print or save any page right now: https://www.printfriendly.com/print?url=ENCODED_PAGE_URL (replace ENCODED_PAGE_URL with the page address, URL-encoded). - Use PrintFriendly from Claude or another AI assistant without writing code: https://www.printfriendly.com/help/ai-assistants ## More - OpenAPI 3.1 description: https://www.printfriendly.com/openapi.json - Postman workspace, every endpoint ready to run (set the `bearerToken` variable to your key, then send GET /v2/usage): https://www.postman.com/printfriendly/printfriendly-api - Human-readable docs: https://www.printfriendly.com/api/instructions/v2 - Get a key: https://www.printfriendly.com/me/api-key - Support: https://www.printfriendly.com/support