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

SurfaceAddressCredential
REST APIhttps://api.prettypdfprinter.com/v1X-API-Key: ppdf_...
MCP serverhttps://mcp.prettypdfprinter.com/mcpAuthorization: Bearer ppdf_...

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.

WhereHeaderNotes
REST APIX-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 serverAuthorization: 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:

FieldTypeDescription
contentstring, requiredThe document text. Markdown up to 1 MiB, HTML up to 2 MiB. HTML is sanitised: scripts and unsafe URLs are removed.
content_typestringmarkdown (default) or html.
profile_idstringA 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.
localestringA tag from the locale list. Default en-GB.
titlestring, max 200Used in the file name of a download link.
deliverystringpdf (default): the PDF is the response body. link: a JSON reply with a time-limited download address.
cleanupboolean, default falseOpt-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.

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.

RequestDescription
GET /v1/company/api-keysList the company's keys. The key value is never returned.
POST /v1/company/api-keysCreate 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.

Tools reference

whoami

Show which company and scopes this connection is using.

list_document_profiles

List this company's document profiles.

render_document

Render Markdown or HTML into a PDF in the company's design.

convert_file

Convert a Word, Excel or PowerPoint file, sent as base64, into a branded PDF.

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.

validate_content

Check content before rendering. Nothing is rendered, stored or counted as a render.

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:

TagLanguage and regionPaperCurrency
en-USEnglish, United StatesLetterUSD
en-GBEnglish, United Kingdom (default)A4GBP
en-IEEnglish, IrelandA4EUR
de-DEGerman, GermanyA4EUR
de-ATGerman, AustriaA4EUR
de-CHGerman, SwitzerlandA4CHF
sl-SISlovenian, SloveniaA4EUR
hr-HRCroatian, CroatiaA4EUR
fr-FRFrench, FranceA4EUR
it-ITItalian, ItalyA4EUR
es-ESSpanish, SpainA4EUR
nl-NLDutch, NetherlandsA4EUR
pl-PLPolish, PolandA4PLN

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.

StatusCodeMeaning
401(Invalid API key)The key is unknown, malformed, revoked or expired.
403insufficient_scopeThe credential lacks the scope the route needs.
403account_not_activatedNo verified manager has activated the account yet.
403account_unavailableThe account is suspended or closed.
403no_company, user_not_verifiedA signed-in user without a company, or with an unverified email.
403not_managerThe action needs a company manager (for example creating a key).
404profile_not_foundNo such document profile for your company.
413content_too_large, file_too_largeThe content or file is over the size limit.
422invalid_content_type, invalid_contentcontent_type is not markdown or html, or content is not text.
422unknown_localeThe locale tag is not supported.
422unsupported_file_type, invalid_fileNot a .docx, .xlsx or .pptx file, empty, unreadable, or its content does not match its extension.
429quota_exceededThe plan limit is reached. The detail names the metric and the limit.
500render_failedThe 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.

PlanRenders per monthAPI calls per monthDocument profilesAPI keysLocales
Trial5050121
Starter300300121
Business3,0003,0004103
EnterpriseUnlimitedUnlimitedUnlimitedUnlimitedUnlimited

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"])

Need help or a profile set up for your brand? Request samples or open the dashboard.