Docs
API and MCP
Send content or a Word, Excel or PowerPoint file and get a PDF in your company's brand, document profiles and locale. The REST API and the MCP server use the same rendering and the same plan limits. This page documents what is built today.
Overview
| Surface | Address | Credential |
|---|---|---|
| REST API | https://api.prettypdfprinter.com/v1 | X-API-Key: ppdf_... |
| MCP server | https://mcp.prettypdfprinter.com/mcp | Authorization: Bearer ppdf_... |
- There is one shared MCP endpoint. Your company is taken from the credential you present. It is never part of the address or a tool argument, and the address alone grants no access.
- An account only works after a verified manager has accepted the Terms and activated it. Until then every call is refused with
account_not_activated. Suspended or closed accounts are refused withaccount_unavailable. - The MCP address above is the intended public address. The dashboard Connect page shows the address for your deployment.
Authentication
Create a key in the dashboard under API keys. A key starts with ppdf_, is shown once, is stored only as a hash and can be revoked at any time. Keys can have an expiry (1 to 730 days). Send it as follows.
| Where | Header | Notes |
|---|---|---|
| REST API | X-API-Key: ppdf_... | The REST API reads company keys from this header only. A bearer token on the REST API is the session of a signed-in dashboard user, not an API key. |
| MCP server | Authorization: Bearer ppdf_... | The MCP server reads the key as a bearer token. Every request re-checks the key and the company status, so a revoked key stops working immediately. |
Keys have a scope: render (render documents, read usage and profiles) or admin (also manage keys, image hosts and invitations; admin implies render). A call without the needed scope is refused with insufficient_scope. Keep keys in a secret store, never in source code, a web page or a prompt.
Sign-in (OAuth) status
Not yet enabled. Sign-in with an account (OAuth) for MCP clients is not available today. The server code can verify OAuth access tokens, but it is switched off and no authorization server has been chosen or connected. Today the MCP server accepts company API keys only.
Assistants that accept only an OAuth sign-in for custom connectors cannot connect yet. Which assistants accept a header key is still being confirmed, so a connection from a given product is not promised. A custom agent or a client that lets you set a bearer header can connect now.
POST/v1/render
Render Markdown or HTML into a PDF in the company's design. Scope: render. Each accepted request counts as one render against the monthly plan limit; a render that fails is not counted.
JSON body:
| Field | Type | Description |
|---|---|---|
content | string, required | The document text. Markdown up to 1 MiB, HTML up to 2 MiB. HTML is sanitised: scripts and unsafe URLs are removed. |
content_type | string | markdown (default) or html. |
profile_id | string | A document profile of your company. Omit it to use the company default. An unknown id, or an id of another company, gives profile_not_found. |
locale | string | A tag from the locale list. Default en-GB. |
title | string, max 200 | Used in the file name of a download link. |
delivery | string | pdf (default): the PDF is the response body. link: a JSON reply with a time-limited download address. |
cleanup | boolean, default false | Opt-in. Sends the Markdown text to an AI model provider to repair formatting. The original text is used if the result changes the content. See Data handling. |
With delivery set to pdf the response is application/pdf with Cache-Control: no-store. With link the response is JSON:
{"download_url": "https://api.prettypdfprinter.com/v1/dl/TOKEN", "expires_at": "2026-11-06T15:00:00Z",
"filename": "Offer-2026-118.pdf", "pages": 2, "size_bytes": 48211, "locale": "de-AT"}
The download address is the only credential needed to fetch the file (GET /v1/dl/{token}), expires after one hour by default and answers 404 once expired.
POST/v1/render/file
Render an uploaded Word (.docx), Excel (.xlsx) or PowerPoint (.pptx) file. Multipart form with the field file (required) and the optional fields profile_id, locale, title, delivery and cleanup, which behave as above. Scope: render.
- Files are limited to 20 MB. The converted text is limited to 1 MiB.
- Embedded images are kept while they fit (200 KB each, 600 KB in total); the rest are replaced by their description. Excel is converted as tables.
- The file type is checked against its content, not only its name.
POST/v1/render/template
Fill one of your company's fixed-layout templates with structured data. Templates are set up by Pretty PDF. List yours with GET /v1/document-templates. Send template, data and optionally locale, title and delivery. Totals and VAT are calculated for you; any totals you send are ignored.
GET/v1/usage
This month's renders and API calls, the plan limits, and how many keys and document profiles exist. Scope: render. GET /v1/usage/history?months=6 returns monthly totals, oldest first (1 to 24 months).
{"period": "2026-11", "plan": "starter", "renders": 12, "api_calls": 15,
"limits": {"renders_per_month": 300, "api_calls_per_month": 300, "max_document_profiles": 1,
"max_api_keys": 2, "max_locales": 1},
"api_keys": 1, "document_profiles": 1}
A limit of null means unlimited.
/v1/company/api-keys
Scope: admin. The company console in the dashboard uses these routes.
| Request | Description |
|---|---|
GET /v1/company/api-keys | List the company's keys. The key value is never returned. |
POST /v1/company/api-keys | Create a key. Body: name (1 to 100 characters), scopes (subset of render, admin; default ["render"]), expires_in_days (optional, 1 to 730). The reply contains the key once, in key. Refused with quota_exceeded when the plan's key limit is reached. |
DELETE /v1/company/api-keys/{key_id} | Revoke a key. Reply {"revoked": true}; 404 if the key is not the company's. |
Creating and revoking keys is limited to a signed-in company manager. A request authenticated by an API key is refused with not_manager, so a leaked key cannot create more keys.
MCP server
Transport: Streamable HTTP at https://mcp.prettypdfprinter.com/mcp. Add it to your assistant as a custom connector or remote MCP server and, where the client supports a header, set Authorization to Bearer followed by a space and your key. See the sign-in status above.
- Every tool call counts as one API call against the monthly plan limit. A tool that renders also counts as one render. Over the limit, the tool returns a message that names the limit.
- Rendering tools return a time-limited download link (one hour by default), not the file itself.
- Each call writes a metadata-only audit row (the tool name, never the content).
Tools reference
whoami
Show which company and scopes this connection is using.
- Parameters: No parameters.
- Returns:
company_idandscopes.
list_document_profiles
List this company's document profiles.
- Parameters: No parameters.
- Returns: A list with
id,name,template,page_size,orientationandis_default.
render_document
Render Markdown or HTML into a PDF in the company's design.
- Parameters:
content(string, required);content_type(markdowndefault, orhtml);profile_id(optional; omit for the company default);locale(optional tag such assl-SI; defaulten-GB);title(optional). - Returns:
download_url,expires_at,filename,pages,size_bytes,locale.
convert_file
Convert a Word, Excel or PowerPoint file, sent as base64, into a branded PDF.
- Parameters:
filename(string, required; its extension decides the type);file_base64(string, required; about 3 MB of file at most);profile_id,locale,title(optional, as above). - Returns: Same as
render_document. For larger files usePOST /v1/render/file.
list_document_templates
List the fixed-layout templates set up for your company, with the fields each one needs and an example of the data.
render_from_template
Fill a fixed-layout template with structured data and get a temporary download link. Send only the facts: the layout is locked, and totals and VAT are calculated by Pretty PDF.
- Parameters:
template,data, optionallocaleandtitle.
validate_content
Check content before rendering. Nothing is rendered, stored or counted as a render.
- Parameters:
content(string, required);content_type(markdowndefault, orhtml). - Returns:
{"valid": true, "remote_images": [...], "sanitized_bytes": n}, or{"valid": false, "code": ..., "message": ...}.
A tool to fetch an earlier document is not available: documents are not kept after rendering other than the temporary link.
Locales
A locale decides paper size, number, date and currency formats, the fixed labels in the document language and which company disclosure fields a business document should show. Formats come from CLDR data. Thirteen tags are supported:
| Tag | Language and region | Paper | Currency |
|---|---|---|---|
en-US | English, United States | Letter | USD |
en-GB | English, United Kingdom (default) | A4 | GBP |
en-IE | English, Ireland | A4 | EUR |
de-DE | German, Germany | A4 | EUR |
de-AT | German, Austria | A4 | EUR |
de-CH | German, Switzerland | A4 | CHF |
sl-SI | Slovenian, Slovenia | A4 | EUR |
hr-HR | Croatian, Croatia | A4 | EUR |
fr-FR | French, France | A4 | EUR |
it-IT | Italian, Italy | A4 | EUR |
es-ES | Spanish, Spain | A4 | EUR |
nl-NL | Dutch, Netherlands | A4 | EUR |
pl-PL | Polish, Poland | A4 | PLN |
A language without a region resolves to a default: en to en-GB, de to de-DE, and likewise sl, hr, fr, it, es, nl, pl. Any other tag is refused with unknown_locale; it never falls back silently.
We format documents correctly; your lawyer or accountant decides what they must say. The label translations are drafts pending review by native speakers, and the per-country lists of company disclosure fields are informational defaults that your accountant or lawyer must confirm. Nothing here is legal advice.
Errors
Errors from the REST API have the shape {"detail": {"code": "...", "message": "..."}, "status_code": 422}. A bad key gives "detail": "Invalid API key" with status 401 and the same message for an unknown, malformed, revoked or expired key. MCP tools report the message as a tool error.
| Status | Code | Meaning |
|---|---|---|
| 401 | (Invalid API key) | The key is unknown, malformed, revoked or expired. |
| 403 | insufficient_scope | The credential lacks the scope the route needs. |
| 403 | account_not_activated | No verified manager has activated the account yet. |
| 403 | account_unavailable | The account is suspended or closed. |
| 403 | no_company, user_not_verified | A signed-in user without a company, or with an unverified email. |
| 403 | not_manager | The action needs a company manager (for example creating a key). |
| 404 | profile_not_found | No such document profile for your company. |
| 413 | content_too_large, file_too_large | The content or file is over the size limit. |
| 422 | invalid_content_type, invalid_content | content_type is not markdown or html, or content is not text. |
| 422 | unknown_locale | The locale tag is not supported. |
| 422 | unsupported_file_type, invalid_file | Not a .docx, .xlsx or .pptx file, empty, unreadable, or its content does not match its extension. |
| 429 | quota_exceeded | The plan limit is reached. The detail names the metric and the limit. |
| 500 | render_failed | The document could not be rendered. The render is not counted. |
Plan limits
Limits apply per company and per calendar month (UTC). They are read from the plan assigned to the company; the prices of the plans are on the pricing page.
| Plan | Renders per month | API calls per month | Document profiles | API keys | Locales |
|---|---|---|---|---|---|
| Trial | 50 | 50 | 1 | 2 | 1 |
| Starter | 300 | 300 | 1 | 2 | 1 |
| Business | 3,000 | 3,000 | 4 | 10 | 3 |
| Enterprise | Unlimited | Unlimited | Unlimited | Unlimited | Unlimited |
A render counts against renders. Every MCP tool call counts against API calls. Check your own figures with GET /v1/usage.
Examples
curl: Markdown to PDF
curl -X POST https://api.prettypdfprinter.com/v1/render \
-H "X-API-Key: $PRETTY_PDF_KEY" \
-H "Content-Type: application/json" \
-d '{"content": "# Offer 2026-118\n\nThank you for your enquiry.",
"content_type": "markdown",
"locale": "de-AT",
"title": "Offer 2026-118",
"delivery": "pdf"}' \
-o offer.pdf
curl: Excel file to PDF
curl -X POST https://api.prettypdfprinter.com/v1/render/file \
-H "X-API-Key: $PRETTY_PDF_KEY" \
-F [email protected] \
-F locale=en-GB \
-F delivery=pdf \
-o datasheet.pdf
Python
import os
import requests
resp = requests.post(
"https://api.prettypdfprinter.com/v1/render",
headers={"X-API-Key": os.environ["PRETTY_PDF_KEY"]},
json={"content": "# Offer 2026-118\n\nThank you for your enquiry.", "locale": "sl-SI"},
timeout=60,
)
if resp.status_code == 200:
with open("offer.pdf", "wb") as f:
f.write(resp.content)
else:
print(resp.status_code, resp.json()["detail"])