MintPDF
Transforme Markdown ou HTML em um PDF estilizado, ou renderize qualquer página web pública, e receba um link de download de volta.
Documentação
MintPDF
HTML & Markdown → PDF, como uma API REST e um servidor MCP.
Sem editor de templates. Sem IDs de templates. Sem dashboard. Sem cadastro para testar.
Experimente · Configuração do MCP · API · Self-host · Segurança · Limitações
Envie HTML ou Markdown, receba um PDF de volta. Por baixo dos panos é Chromium, com o CSS de impressão já resolvido para que tabelas não sejam divididas entre páginas, cabeçalhos de tabela se repitam e Markdown saia com aparência de documento, não de arquivo de texto.
- Paginação é o ponto.
break-inside, repetição dethead, órfãs e viúvas, e cabeçalhos e rodapés que realmente herdam seu estilo. Veja Como funciona. - MCP nativo —
generate_pdfepdf_from_urlvia HTTP streamable, para que um agente possa produzir um documento no meio da conversa. - Documentos não são armazenados — arquivos renderizados são excluídos após uma hora, e seu conteúdo nunca é registrado. Chaves, e-mails e contadores de uso obviamente são armazenados. Veja Segurança.
- Execute você mesmo — MIT, com imagem publicada. O serviço hospedado existe para você não precisar operar Chromium, não porque o renderizador é secreto.
Início rápido
Sem cadastro, sem chave:
curl -X POST https://mintpdf.dev/v1/pdf \
-H "Content-Type: application/json" \
-d '{"markdown":"# Invoice #42\n\n| Item | Price |\n|---|---|\n| Widget | $9.00 |","pageNumbers":true}' \
--output invoice.pdf
Quer mais de 10 renderizações por dia? Uma chave gratuita (apenas e-mail, sem cartão) eleva para 100 por mês:
curl -X POST https://mintpdf.dev/v1/keys \
-H "Content-Type: application/json" \
-d '{"email":"you@example.com"}'
# → {"key":"pm_…","daily_limit":100} # 100 renders per month
Depois envie Authorization: Bearer pm_… com suas solicitações.
Use com Claude (ou qualquer cliente MCP)
{
"mcpServers": {
"mintpdf": {
"command": "npx",
"args": ["-y", "mintpdf-mcp"]
}
}
}
Prefere o endpoint hospedado diretamente? Use mcp-remote em vez disso:
{
"mcpServers": {
"mintpdf": {
"command": "npx",
"args": ["-y", "mcp-remote", "https://mintpdf.dev/mcp"]
}
}
}
Reinicie seu cliente e é só pedir:
"Resuma este tópico como um briefing de uma página com números de página e me dê um PDF."
| Ferramenta | Entrada | Retorna |
|---|---|---|
generate_pdf | html ou markdown, além de opções | URL de download, válida por 1 hora |
pdf_from_url | url (http/https público), além de opções | URL de download, válida por 1 hora |
API
POST /v1/pdf
O corpo aceita exatamente uma fonte, além de opções:
| Campo | Tipo | Observações |
|---|---|---|
html | string | Documento completo ou fragmento |
markdown | string | Renderizado com a folha de estilo padrão |
url | string | Página pública para renderizar. Endereços privados/internos são bloqueados |
format | string | A4 (padrão), Letter, Legal, A3, A5 |
landscape | boolean | padrão false |
margin | string | todos os lados, ex.: "18mm" |
headerText / footerText | string | texto pequeno em todas as páginas |
pageNumbers | boolean | adiciona 3 / 7 ao rodapé |
output | "pdf" | "url" | padrão retorna bytes de PDF; "url" retorna JSON com um link |
POST /v1/keys
{"email":"you@example.com"} → uma chave gratuita. Sem cartão, sem loop de verificação.
POST /mcp
Endpoint MCP streamable-HTTP, sem estado. Mesmas capacidades da API REST.
Postman
Uma coleção pronta cobrindo todos os endpoints e opções está em
postman/. Importe pelo link:
https://raw.githubusercontent.com/TrendTweekers/mintpdf/main/postman/mintpdf.postman_collection.json
A primeira solicitação roda sem chave alguma, e buscar uma chave gratuita a armazena na variável da coleção automaticamente, então o restante da coleção funciona logo em seguida.
Limites
| Nível | Limite | Preço |
|---|---|---|
| Anônimo | 10 renderizações/dia por IP | grátis, sem cadastro |
| Chave gratuita | 100 renderizações/mês | grátis, apenas e-mail |
| Solo | 3.000 renderizações/mês | US$ 19/mês |
| Equipe | 12.000 renderizações/mês | US$ 49/mês |
| Escala | 50.000 renderizações/mês | US$ 129/mês |
Self-host
MintPDF é licenciado sob MIT; execute o seu próprio se preferir.
npm install
npm run build
npm start # http://localhost:3000
node dist/smoke.js # end-to-end render check
Ou puxe a imagem publicada, que já vem com Chromium e as fontes embutidas:
docker run -p 3000:3000 \
-e BASE_URL=http://localhost:3000 \
-e DATA_DIR=/data -v mintpdf-data:/data \
ghcr.io/trendtweekers/mintpdf:latest
Então é a mesma API na sua máquina, sem limites e sem nada saindo dela:
curl -X POST http://localhost:3000/v1/pdf \
-H "Content-Type: application/json" \
-d '{"markdown":"# Local","pageNumbers":true}' --output local.pdf
As imagens são construídas e publicadas por CI a cada mudança, e cada uma é testada iniciando o
contêiner e renderizando um PDF real antes de ser marcada. As tags são latest e o SHA curto
do commit. Construir você mesmo também funciona:
docker build -t mintpdf .
docker run -p 3000:3000 -e BASE_URL=http://localhost:3000 mintpdf
Ambiente: BASE_URL (usado em links de download), DATA_DIR (padrão /tmp/mintpdf; monte um
volume para persistir chaves), ANON_DAILY_LIMIT, FREE_MONTHLY_LIMIT, SOLO_MONTHLY_LIMIT,
TEAM_MONTHLY_LIMIT, SCALE_MONTHLY_LIMIT, OVERAGE_FACTOR, RENDER_CONCURRENCY,
RENDER_QUEUE, RENDER_QUEUE_WAIT_MS.
Controle de carga e admissão
Cada renderização é uma aba do Chromium, então a memória limita a concorrência muito antes de CPU ou custo. Sem limite, um pico de tráfego abre uma aba por solicitação até o reaper de OOM matar o contêiner e toda solicitação falhar, incluindo as quase concluídas. Medido aqui: 30 renderizações concorrentes sem portão deixaram 30 processos Chrome órfãos e uma máquina inutilizável.
RENDER_CONCURRENCY renderizações rodam de uma vez, RENDER_QUEUE mais podem esperar, e qualquer coisa além disso é
recusada imediatamente com 503 e um Retry-After em vez de ser permitida acumular. Virar
alguns chamadores para fora em menos de um segundo é estritamente melhor do que servir a todos um timeout.
Os padrões de código são conservadores (3 e 20). Medido em uma pequena instância Railway em 10 e 70:
| Rajada | Atendidas | Recusadas | Mediana | Tempo total | Instância após |
|---|---|---|---|---|---|
| 45 | 45 | 0 | 1,5s | 2,3s | saudável |
| 120 | 81 | 39 | 3,0s | 4,0s | saudável |
| 250 | 80 | 170 | 3,4s | 4,4s | saudável, homepage 0,24s |
Aproximadamente 18 renderizações por segundo sustentadas, com a parcela recusada respondida em menos de 2,7s. Aumente os números apenas com base em medição no tamanho da sua própria instância, nunca na esperança.
Se você quiser uma cadeia de ferramentas PDF self-host mais completa (formatos Office, mesclagem, divisão), Gotenberg é excelente e faz mais do que isto.
Segurança
Este serviço renderiza HTML e URLs fornecidos por qualquer pessoa, então as perguntas interessantes são sobre o que esse conteúdo pode alcançar.
O HTML enviado executa JavaScript. Ele precisa: diagramas Mermaid e matemática KaTeX são renderizados na página. Trate o renderizador como executando código não confiável, e é por isso que as restrições de rede abaixo importam mais do que importariam para um conversor estático.
SSRF é bloqueado em duas camadas.
- Um
urlenviado é analisado, restrito ahttp/https, e resolvido. Se qualquer endereço resolvido for privado, a solicitação é recusada com um 400 antes que um navegador seja envolvido. - Independentemente, toda solicitação que o Chromium faz é interceptada e o destino resolvido novamente no
momento da solicitação, então bloqueado se privado. Isso cobre imagens embutidas, folhas de estilo, fontes,
redirecionamentos e
fetch()de JavaScript enviado, não apenas a URL que você pediu.
A segunda camada resolve em vez de confiar no hostname, e armazena em cache apenas recusas, nunca aprovações: armazenar em cache "este host é público" reabriria exatamente o buraco que a verificação existe para fechar. Nomes não resolvíveis falham de forma fechada.
Sendo preciso sobre o que isso faz e não faz: uma tentativa de DNS-rebinding não pode mais esperar uma aprovação em cache, então precisa vencer uma corrida entre esta consulta e a do próprio Chromium, em cada solicitação. Isso é um alvo muito mais estreito do que uma janela fixa, mas é uma corrida estreitada, não uma porta fechada. Eliminá-la completamente significa fixar o endereço resolvido na camada de socket, o que não está implementado.
Bloqueados: loopback, 0.0.0.0, RFC1918, CGNAT (100.64/10), link-local e metadados de nuvem
(169.254.169.254); IPv6 loopback, não especificado, link-local, site-local, unique-local, multicast,
NAT64 e Teredo; formas IPv4-mapped e IPv4-compatible em qualquer grafia, então ::ffff:10.0.0.1
e ::ffff:a00:1 são o mesmo endereço e ambos são recusados; nomes localhost/.local/.internal;
qualquer hostname público que resolva para um endereço privado; e todo esquema exceto http, https,
data e blob.
Endereços são julgados pelos seus bytes em vez de correspondência de texto, porque o mesmo endereço tem muitas grafias e uma correspondência de texto pega uma e perde o resto.
Há uma suíte de testes exatamente para isso, e ela é feita para ser executada em vez de confiada:
BASE=https://mintpdf.dev node scratchpad/ssrf_suite.mjs
Ela verifica os bypasses acima e que a renderização comum ainda funciona, porque uma proteção que também bloqueia fontes web é um bug diferente, não uma correção.
O parser IPv6 tem sua própria tabela de literais adversariais, já que uma string inválida silenciosamente se tornando um endereço válido é a falha que importa neste tipo de código:
node scratchpad/ipv6_table_test.mjs
Links de download usam um identificador crypto.randomUUID() e não são autenticados: qualquer pessoa com
o link pode buscar o arquivo durante a hora em que ele existe. Isso é deliberado, para que um link possa ser enviado por e-mail ou
entregue a um navegador, mas significa que o link é o segredo.
Registro de logs. Metadados de solicitação são registrados (método, caminho, status, duração). Corpos de solicitação nunca são registrados, então o HTML e Markdown que você envia não são gravados em nenhum lugar exceto no arquivo temporário. A tabela de análise armazena tipo de evento, caminho, referrer, país e um hash com sal diário do IP. Nenhum conteúdo de documento, e nenhuma maneira de reconstruir um documento a partir disso.
Limitações
Vale saber antes de construir sobre isso.
- Arquivos são excluídos após uma hora. Não há biblioteca de documentos e nenhuma maneira de buscar uma renderização novamente depois. Gere, use, pronto. Se você precisar de permanência, salve os bytes do seu lado.
- Sem formatos Office, mesclagem ou divisão. Isso converte HTML, Markdown e páginas web, e nada mais. Gotenberg é mais maduro e cobre muito mais terreno se você estiver fazendo self-host e precisar disso.
- Uma instância. Chaves e cotas ficam em SQLite em um volume montado, então rodar várias réplicas contra um volume não funcionará. Escala horizontal precisa de um banco de dados real primeiro.
- Renderizações são controladas por admissão. Acima da capacidade, a API retorna
503comRetry-Afterem vez de enfileirar sem limite. Veja a tabela acima para comportamento medido. - Dois dias de idade no momento da escrita, sem usuários pagantes ainda.
Como funciona
TypeScript, Fastify e Puppeteer dirigindo um Chromium compartilhado com uma página por solicitação. node:sqlite
armazena chaves, cotas e eventos, então não há dependências nativas para compilar.
As partes que exigiram o trabalho real são as pouco glamourosas:
- CSS de impressão.
break-inside: avoidem tabelas, linhas, itens de lista, blocos de código, blockquotes e figuras;thead { display: table-header-group }para cabeçalhos se repetirem;orphans/widows;break-after: avoidem títulos para que nenhum fique órfão no rodapé de uma página. - Templates de cabeçalho e rodapé, que são um documento separado da sua página: eles ignoram o CSS da página e renderizam em tamanho de fonte quase zero a menos que os estilos estejam inline, e ficam fora das margens de conteúdo.
- Controle de admissão, porque uma aba Chromium por solicitação concorrente é como o contêiner fica sem memória.
- Isolamento de rede para um renderizador que executa JavaScript não confiável. Veja Segurança.
Licença
MIT — veja LICENSE.