MintPDF

Convierte Markdown o HTML en un PDF con estilo, o renderiza cualquier página web pública, y recibe un enlace de descarga.

Documentación

MintPDF

MintPDF

HTML y Markdown → PDF, como API REST y servidor MCP.

Sin editor de plantillas. Sin IDs de plantillas. Sin panel de control. Sin registro para probar.

mintpdf.dev

Pruébalo · Configuración MCP · API · Autoalojamiento · Seguridad · Limitaciones


Envía HTML o Markdown, recibe un PDF. Utiliza Chromium internamente, con el CSS de impresión ya resuelto para que las tablas no se dividan entre páginas, los encabezados de tabla se repitan y el Markdown se vea como un documento en lugar de un archivo de texto.

  • La paginación es el punto. break-inside, repetición de thead, huérfanas y viudas, y encabezados y pies de página que realmente heredan tu estilo. Consulta Cómo funciona.
  • MCP nativo — generate_pdf y pdf_from_url sobre HTTP transmisible, para que un agente pueda producir un documento en medio de una conversación.
  • Los documentos no se conservan — los archivos renderizados se eliminan después de una hora, y su contenido nunca se registra. Las claves, correos electrónicos y contadores de uso sí se almacenan. Consulta Seguridad.
  • Ejecútalo tú mismo — MIT, con una imagen publicada. El servicio alojado existe para que no tengas que operar Chromium, no porque el renderizador sea secreto.

Inicio rápido

Sin registro, sin clave:

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

¿Quieres más de 3 renderizados al día? Una clave gratuita (solo correo electrónico, sin tarjeta) lo eleva a 100 al mes:

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

Luego envía Authorization: Bearer pm_… con tus solicitudes.

Úsalo desde Claude (o cualquier cliente MCP)

{
  "mcpServers": {
    "mintpdf": {
      "command": "npx",
      "args": ["-y", "mintpdf-mcp"]
    }
  }
}

¿Prefieres el endpoint alojado directamente? Usa mcp-remote en su lugar:

{
  "mcpServers": {
    "mintpdf": {
      "command": "npx",
      "args": ["-y", "mcp-remote", "https://mintpdf.dev/mcp"]
    }
  }
}

Reinicia tu cliente y simplemente pide:

"Resume este hilo como un informe de una página con números de página y dame un PDF."

HerramientaEntradaDevuelve
generate_pdfhtml o markdown, más opcionesURL de descarga, válida 1 hora
pdf_from_urlurl (http/https público), más opcionesURL de descarga, válida 1 hora

API

POST /v1/pdf

El cuerpo acepta exactamente una fuente, más opciones:

CampoTipoNotas
htmlstringDocumento completo o fragmento
markdownstringRenderizado con la hoja de estilos predeterminada
urlstringPágina pública para renderizar. Las direcciones privadas/internas están bloqueadas
formatstringA4 (predeterminado), Letter, Legal, A3, A5
landscapebooleanpredeterminado false
marginstringtodos los lados, p. ej. "18mm"
headerText / footerTextstringtexto pequeño en cada página
pageNumbersbooleanañade 3 / 7 al pie de página
output"pdf" | "url"predeterminado devuelve bytes PDF; "url" devuelve JSON con un enlace

POST /v1/keys

{"email":"you@example.com"} → una clave gratuita. Sin tarjeta, sin bucle de verificación.

POST /mcp

Endpoint MCP de HTTP transmisible, sin estado. Mismas capacidades que la API REST.

Postman

Una colección lista para usar que cubre cada endpoint y opción está en postman/. Impórtala por enlace:

https://raw.githubusercontent.com/TrendTweekers/mintpdf/main/postman/mintpdf.postman_collection.json

La primera solicitud se ejecuta sin clave alguna, y obtener una clave gratuita la almacena en la variable de la colección automáticamente, por lo que el resto de la colección funciona de inmediato.

Límites

NivelLímitePrecio
Anónimo3 renderizados/día por IPgratis, sin registro
Clave gratuita100 renderizados/mesgratis, solo correo electrónico
Solo3,000 renderizados/mes$19/mes
Equipo12,000 renderizados/mes$49/mes
Escala50,000 renderizados/mes$129/mes

Autoalojamiento

MintPDF tiene licencia MIT; ejecuta el tuyo propio si prefieres.

npm install
npm run build
npm start                # http://localhost:3000
node dist/smoke.js       # end-to-end render check

O extrae la imagen publicada, que incluye Chromium y las fuentes integradas:

docker run -p 3000:3000 \
  -e BASE_URL=http://localhost:3000 \
  -e DATA_DIR=/data -v mintpdf-data:/data \
  ghcr.io/trendtweekers/mintpdf:latest

Entonces es la misma API en tu propia máquina, sin límites y sin que nada salga de ella:

curl -X POST http://localhost:3000/v1/pdf \
  -H "Content-Type: application/json" \
  -d '{"markdown":"# Local","pageNumbers":true}' --output local.pdf

Las imágenes se construyen y publican mediante CI en cada cambio, y cada una se prueba con una prueba de humo iniciando el contenedor y renderizando un PDF real antes de etiquetarla. Las etiquetas son latest y el SHA corto del commit. Construirlo tú mismo también funciona:

docker build -t mintpdf .
docker run -p 3000:3000 -e BASE_URL=http://localhost:3000 mintpdf

Entorno: BASE_URL (usado en enlaces de descarga), DATA_DIR (predeterminado a /tmp/mintpdf; monta un volumen para persistir claves), 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.

Control de carga y admisión

Cada renderizado es una pestaña de Chromium, por lo que la memoria limita la concurrencia mucho antes que la CPU o el costo. Sin límite, un pico de tráfico abre una pestaña por solicitud hasta que el reaper de OOM mata el contenedor y cada solicitud falla, incluidas las casi terminadas. Medido aquí: 30 renderizados concurrentes sin puerta dejaron 30 procesos de Chrome huérfanos y una máquina inutilizable.

RENDER_CONCURRENCY renderizados se ejecutan a la vez, RENDER_QUEUE más pueden esperar, y cualquier cosa más allá se rechaza inmediatamente con 503 y un Retry-After en lugar de permitir que se acumulen. Rechazar a algunos llamadores en menos de un segundo es estrictamente mejor que servir a todos un tiempo de espera.

Los valores predeterminados del código son conservadores (3 y 20). Medido en una instancia pequeña de Railway a 10 y 70:

RáfagaServidosRechazadosMedianaTiempo totalInstancia después
454501.5s2.3ssaludable
12081393.0s4.0ssaludable
250801703.4s4.4ssaludable, página de inicio 0.24s

Aproximadamente 18 renderizados por segundo sostenidos, con la parte rechazada respondida en menos de 2.7s. Aumenta los números solo contra una medición en tu propio tamaño de instancia, nunca por esperanza.

Si quieres una cadena de herramientas PDF autoalojada más completa (formatos de Office, fusión, división), Gotenberg es excelente y hace más que esto.

Seguridad

Este servicio renderiza HTML y URLs suministradas por cualquiera, por lo que las preguntas interesantes son sobre qué puede alcanzar ese contenido.

El HTML enviado ejecuta JavaScript. Tiene que hacerlo: los diagramas de Mermaid y las matemáticas de KaTeX se renderizan en la página. Trata al renderizador como si ejecutara código no confiable, por eso las restricciones de red a continuación importan más de lo que lo harían para un convertidor estático.

El SSRF está bloqueado en dos capas.

  1. Un url enviado se analiza, se restringe a http/https, y se resuelve. Si cualquier dirección resuelta es privada, la solicitud se rechaza con un 400 antes de que intervenga un navegador.
  2. Independientemente, cada solicitud que hace Chromium se intercepta y el destino se resuelve de nuevo en el momento de la solicitud, y luego se bloquea si es privado. Esto cubre imágenes incrustadas, hojas de estilo, fuentes, redirecciones y fetch() del JavaScript enviado, no solo la URL que pediste.

La segunda capa resuelve en lugar de confiar en el nombre de host, y almacena en caché solo rechazos, nunca aprobaciones: almacenar en caché "este host es público" reabriría exactamente el agujero que la verificación existe para cerrar. Los nombres no resolubles fallan de manera cerrada.

Ser precisos sobre lo que eso logra y no logra: un intento de rebinding de DNS ya no puede esperar una aprobación en caché, por lo que tiene que ganar una carrera entre esta búsqueda y la propia de Chromium, en cada solicitud. Ese es un objetivo mucho más estrecho que una ventana fija, pero es una carrera estrechada en lugar de una puerta cerrada. Eliminarlo por completo significa fijar la dirección resuelta en la capa de socket, lo cual no está implementado.

Bloqueado: loopback, 0.0.0.0, RFC1918, CGNAT (100.64/10), link-local y metadatos de nube (169.254.169.254); loopback IPv6, no especificado, link-local, site-local, unique-local, multicast, NAT64 y Teredo; formas IPv4-mapeadas e IPv4-compatibles en cualquier ortografía, por lo que ::ffff:10.0.0.1 y ::ffff:a00:1 son la misma dirección y ambas se rechazan; nombres localhost/.local/.internal; cualquier nombre de host público que resuelva a una dirección privada; y cada esquema excepto http, https, data y blob.

Las direcciones se juzgan por sus bytes en lugar de coincidir texto, porque la misma dirección tiene muchas ortografías y una coincidencia de texto atrapa una y pierde el resto.

Hay una suite de pruebas exactamente para esto, y está pensada para ejecutarse en lugar de confiarse:

BASE=https://mintpdf.dev node scratchpad/ssrf_suite.mjs

Verifica los bypasses anteriores y que el renderizado ordinario aún funcione, porque un guardia que también bloquea fuentes web es un error diferente en lugar de una corrección.

El analizador IPv6 tiene su propia tabla de literales adversariales, ya que una cadena inválida que silenciosamente se convierte en una dirección válida es el fallo que importa en este tipo de código:

node scratchpad/ipv6_table_test.mjs

Los enlaces de descarga usan un identificador crypto.randomUUID() y no están autenticados: cualquiera con el enlace puede obtener el archivo durante la hora que existe. Eso es deliberado, para que un enlace pueda enviarse por correo o entregarse a un navegador, pero significa que el enlace es el secreto.

Registro. Los metadatos de solicitud se registran (método, ruta, estado, duración). Los cuerpos de solicitud nunca se registran, por lo que el HTML y Markdown que envías no se escriben en ningún lugar excepto en el archivo temporal. La tabla de análisis almacena tipo de evento, ruta, referente, país y un hash salado diario de la IP. Sin contenido de documento, y sin forma de reconstruir un documento a partir de ello.

Limitaciones

Vale la pena saberlo antes de construir sobre ello.

  • Los archivos se eliminan después de una hora. No hay biblioteca de documentos ni forma de obtener un renderizado nuevamente más tarde. Genera, usa, listo. Si necesitas permanencia, guarda los bytes en tu lado.
  • Sin formatos de Office, fusión o división. Esto convierte HTML, Markdown y páginas web, y nada más. Gotenberg es más maduro y cubre mucho más terreno si te autoalojas y lo necesitas.
  • Una instancia. Las claves y cuotas viven en SQLite en un volumen montado, por lo que ejecutar varias réplicas contra un volumen no funcionará. El escalado horizontal necesita una base de datos real primero.
  • Los renderizados están controlados por admisión. Sobre capacidad, la API devuelve 503 con Retry-After en lugar de poner en cola sin límite. Consulta la tabla anterior para el comportamiento medido.
  • Dos días de antigüedad al momento de escribir esto, sin usuarios de pago aún.

Cómo funciona

TypeScript, Fastify y Puppeteer manejando un Chromium compartido con una página por solicitud. node:sqlite contiene claves, cuotas y eventos, por lo que no hay dependencias nativas que construir.

Las partes que tomaron el trabajo real son las poco glamorosas:

  • CSS de impresión. break-inside: avoid en tablas, filas, elementos de lista, bloques de código, blockquotes y figuras; thead { display: table-header-group } para que los encabezados se repitan; orphans/widows; break-after: avoid en encabezados para que ninguno quede varado al pie de una página.
  • Plantillas de encabezado y pie de página, que son un documento separado de tu página: ignoran el CSS de la página y se renderizan a un tamaño de fuente casi cero a menos que los estilos estén en línea, y están fuera de los márgenes de contenido.
  • Control de admisión, porque una pestaña de Chromium por solicitud concurrente es cómo el contenedor se queda sin memoria.
  • Aislamiento de red para un renderizador que ejecuta JavaScript no confiable. Consulta Seguridad.

Licencia

MIT — consulta LICENSE.