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

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.

mintpdf.dev

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 de thead, órfãs e viúvas, e cabeçalhos e rodapés que realmente herdam seu estilo. Veja Como funciona.
  • MCP nativogenerate_pdf e pdf_from_url via 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."

FerramentaEntradaRetorna
generate_pdfhtml ou markdown, além de opçõesURL de download, válida por 1 hora
pdf_from_urlurl (http/https público), além de opçõesURL de download, válida por 1 hora

API

POST /v1/pdf

O corpo aceita exatamente uma fonte, além de opções:

CampoTipoObservações
htmlstringDocumento completo ou fragmento
markdownstringRenderizado com a folha de estilo padrão
urlstringPágina pública para renderizar. Endereços privados/internos são bloqueados
formatstringA4 (padrão), Letter, Legal, A3, A5
landscapebooleanpadrão false
marginstringtodos os lados, ex.: "18mm"
headerText / footerTextstringtexto pequeno em todas as páginas
pageNumbersbooleanadiciona 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ívelLimitePreço
Anônimo10 renderizações/dia por IPgrátis, sem cadastro
Chave gratuita100 renderizações/mêsgrátis, apenas e-mail
Solo3.000 renderizações/mêsUS$ 19/mês
Equipe12.000 renderizações/mêsUS$ 49/mês
Escala50.000 renderizações/mêsUS$ 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:

RajadaAtendidasRecusadasMedianaTempo totalInstância após
454501,5s2,3ssaudável
12081393,0s4,0ssaudável
250801703,4s4,4ssaudá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.

  1. Um url enviado é analisado, restrito a http/https, e resolvido. Se qualquer endereço resolvido for privado, a solicitação é recusada com um 400 antes que um navegador seja envolvido.
  2. 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 503 com Retry-After em 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: avoid em 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: avoid em 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.