{ "openapi": "3.0.3", "info": { "title": "DocFast API", "version": "1.0.0", "description": "Convert HTML, Markdown, and URLs to pixel-perfect PDFs. Built-in invoice & receipt templates.\n\n## Authentication\nAll conversion and template endpoints require an API key via `Authorization: Bearer ` or `X-API-Key: ` header.\n\n## Demo Endpoints\nTry the API without signing up! Demo endpoints are public (no API key needed) but rate-limited to 5 requests/hour per IP and produce watermarked PDFs.\n\n## Rate Limits\n- Demo: 5 PDFs/hour per IP (watermarked)\n- Pro tier: 5,000 PDFs/month, 30 req/min\n\n## Getting Started\n1. Try the demo at `POST /v1/demo/html` — no signup needed\n2. Subscribe to Pro at [docfast.dev](https://docfast.dev/#pricing) for clean PDFs\n3. Use your API key to convert documents", "contact": { "name": "DocFast", "url": "https://docfast.dev", "email": "support@docfast.dev" } }, "servers": [ { "url": "https://docfast.dev", "description": "Production" } ], "tags": [ { "name": "Demo", "description": "Try the API without signing up — watermarked PDFs, rate-limited" }, { "name": "Conversion", "description": "Convert HTML, Markdown, or URLs to PDF (requires API key)" }, { "name": "Templates", "description": "Built-in document templates" }, { "name": "Account", "description": "Key recovery and email management" }, { "name": "Billing", "description": "Stripe-powered subscription management" }, { "name": "System", "description": "Health checks and usage stats" } ], "components": { "securitySchemes": { "BearerAuth": { "type": "http", "scheme": "bearer", "description": "API key as Bearer token" }, "ApiKeyHeader": { "type": "apiKey", "in": "header", "name": "X-API-Key", "description": "API key via X-API-Key header" } }, "schemas": { "PdfOptions": { "type": "object", "properties": { "format": { "type": "string", "enum": [ "A4", "Letter", "Legal", "A3", "A5", "Tabloid" ], "default": "A4", "description": "Page size" }, "landscape": { "type": "boolean", "default": false, "description": "Landscape orientation" }, "margin": { "type": "object", "properties": { "top": { "type": "string", "description": "Top margin (e.g. \"10mm\", \"1in\")", "default": "0" }, "right": { "type": "string", "description": "Right margin", "default": "0" }, "bottom": { "type": "string", "description": "Bottom margin", "default": "0" }, "left": { "type": "string", "description": "Left margin", "default": "0" } }, "description": "Page margins" }, "printBackground": { "type": "boolean", "default": true, "description": "Print background colors and images" }, "filename": { "type": "string", "description": "Custom filename for Content-Disposition header", "default": "document.pdf" } } }, "Error": { "type": "object", "properties": { "error": { "type": "string", "description": "Error message" } }, "required": [ "error" ] } } }, "paths": { "/v1/usage/me": { "get": { "summary": "Get your usage stats", "tags": [ "Account" ], "security": [ { "ApiKeyHeader": [] }, { "BearerAuth": [] } ], "responses": { "200": { "description": "Usage statistics for the authenticated user", "content": { "application/json": { "schema": { "type": "object", "properties": { "used": { "type": "integer", "description": "PDFs generated this month" }, "limit": { "type": "integer", "description": "Monthly PDF limit for your plan" }, "plan": { "type": "string", "enum": [ "demo", "pro" ], "description": "Current plan" }, "month": { "type": "string", "description": "Current billing month (YYYY-MM)" } } } } } }, "401": { "description": "Missing or invalid API key" } } } }, "/v1/billing/checkout": { "post": { "tags": [ "Billing" ], "summary": "Create a Stripe checkout session", "description": "Creates a Stripe Checkout session for a Pro subscription (€9/month).\nReturns a URL to redirect the user to Stripe's hosted payment page.\nRate limited to 3 requests per hour per IP.\n", "responses": { "200": { "description": "Checkout session created", "content": { "application/json": { "schema": { "type": "object", "properties": { "url": { "type": "string", "format": "uri", "description": "Stripe Checkout URL to redirect the user to" } } } } } }, "413": { "description": "Request body too large" }, "429": { "description": "Too many checkout requests" }, "500": { "description": "Failed to create checkout session" } } } }, "/v1/convert/html": { "post": { "tags": [ "Conversion" ], "summary": "Convert HTML to PDF", "description": "Converts HTML content to a PDF document. Bare HTML fragments are automatically wrapped in a full HTML document.", "security": [ { "BearerAuth": [] }, { "ApiKeyHeader": [] } ], "requestBody": { "required": true, "content": { "application/json": { "schema": { "allOf": [ { "type": "object", "required": [ "html" ], "properties": { "html": { "type": "string", "description": "HTML content to convert. Can be a full document or a fragment.", "example": "

Hello World

My first PDF

" }, "css": { "type": "string", "description": "Optional CSS to inject (only used when html is a fragment, not a full document)", "example": "body { font-family: sans-serif; padding: 40px; }" } } }, { "$ref": "#/components/schemas/PdfOptions" } ] } } } }, "responses": { "200": { "description": "PDF document", "headers": { "X-RateLimit-Limit": { "$ref": "#/components/headers/X-RateLimit-Limit" }, "X-RateLimit-Remaining": { "$ref": "#/components/headers/X-RateLimit-Remaining" }, "X-RateLimit-Reset": { "$ref": "#/components/headers/X-RateLimit-Reset" } }, "content": { "application/pdf": { "schema": { "type": "string", "format": "binary" } } } }, "400": { "description": "Missing html field" }, "401": { "description": "Missing API key" }, "403": { "description": "Invalid API key" }, "415": { "description": "Unsupported Content-Type (must be application/json)" }, "429": { "description": "Rate limit or usage limit exceeded", "headers": { "Retry-After": { "$ref": "#/components/headers/Retry-After" } } }, "500": { "description": "PDF generation failed" } } } }, "/v1/convert/markdown": { "post": { "tags": [ "Conversion" ], "summary": "Convert Markdown to PDF", "description": "Converts Markdown content to HTML and then to a PDF document.", "security": [ { "BearerAuth": [] }, { "ApiKeyHeader": [] } ], "requestBody": { "required": true, "content": { "application/json": { "schema": { "allOf": [ { "type": "object", "required": [ "markdown" ], "properties": { "markdown": { "type": "string", "description": "Markdown content to convert", "example": "# Hello World\\n\\nThis is **bold** and *italic*." }, "css": { "type": "string", "description": "Optional CSS to inject into the rendered HTML" } } }, { "$ref": "#/components/schemas/PdfOptions" } ] } } } }, "responses": { "200": { "description": "PDF document", "headers": { "X-RateLimit-Limit": { "$ref": "#/components/headers/X-RateLimit-Limit" }, "X-RateLimit-Remaining": { "$ref": "#/components/headers/X-RateLimit-Remaining" }, "X-RateLimit-Reset": { "$ref": "#/components/headers/X-RateLimit-Reset" } }, "content": { "application/pdf": { "schema": { "type": "string", "format": "binary" } } } }, "400": { "description": "Missing markdown field" }, "401": { "description": "Missing API key" }, "403": { "description": "Invalid API key" }, "415": { "description": "Unsupported Content-Type" }, "429": { "description": "Rate limit or usage limit exceeded", "headers": { "Retry-After": { "$ref": "#/components/headers/Retry-After" } } }, "500": { "description": "PDF generation failed" } } } }, "/v1/convert/url": { "post": { "tags": [ "Conversion" ], "summary": "Convert URL to PDF", "description": "Fetches a URL and converts the rendered page to PDF. JavaScript is disabled for security.\nPrivate/internal IP addresses are blocked (SSRF protection). DNS is pinned to prevent rebinding.\n", "security": [ { "BearerAuth": [] }, { "ApiKeyHeader": [] } ], "requestBody": { "required": true, "content": { "application/json": { "schema": { "allOf": [ { "type": "object", "required": [ "url" ], "properties": { "url": { "type": "string", "format": "uri", "description": "URL to convert (http or https only)", "example": "https://example.com" }, "waitUntil": { "type": "string", "enum": [ "load", "domcontentloaded", "networkidle0", "networkidle2" ], "default": "domcontentloaded", "description": "When to consider navigation finished" } } }, { "$ref": "#/components/schemas/PdfOptions" } ] } } } }, "responses": { "200": { "description": "PDF document", "headers": { "X-RateLimit-Limit": { "$ref": "#/components/headers/X-RateLimit-Limit" }, "X-RateLimit-Remaining": { "$ref": "#/components/headers/X-RateLimit-Remaining" }, "X-RateLimit-Reset": { "$ref": "#/components/headers/X-RateLimit-Reset" } }, "content": { "application/pdf": { "schema": { "type": "string", "format": "binary" } } } }, "400": { "description": "Missing/invalid URL or URL resolves to private IP" }, "401": { "description": "Missing API key" }, "403": { "description": "Invalid API key" }, "415": { "description": "Unsupported Content-Type" }, "429": { "description": "Rate limit or usage limit exceeded", "headers": { "Retry-After": { "$ref": "#/components/headers/Retry-After" } } }, "500": { "description": "PDF generation failed" } } } }, "/v1/demo/html": { "post": { "tags": [ "Demo" ], "summary": "Convert HTML to PDF (demo)", "description": "Public endpoint — no API key required. Rate limited to 5 requests per hour per IP.\nOutput PDFs include a DocFast watermark. Upgrade to Pro for clean output.\n", "requestBody": { "required": true, "content": { "application/json": { "schema": { "allOf": [ { "type": "object", "required": [ "html" ], "properties": { "html": { "type": "string", "description": "HTML content to convert", "example": "

Hello World

My first PDF

" }, "css": { "type": "string", "description": "Optional CSS to inject (used when html is a fragment)" } } }, { "$ref": "#/components/schemas/PdfOptions" } ] } } } }, "responses": { "200": { "description": "Watermarked PDF document", "headers": { "X-RateLimit-Limit": { "$ref": "#/components/headers/X-RateLimit-Limit" }, "X-RateLimit-Remaining": { "$ref": "#/components/headers/X-RateLimit-Remaining" }, "X-RateLimit-Reset": { "$ref": "#/components/headers/X-RateLimit-Reset" } }, "content": { "application/pdf": { "schema": { "type": "string", "format": "binary" } } } }, "400": { "description": "Missing html field", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } } }, "415": { "description": "Unsupported Content-Type" }, "429": { "description": "Demo rate limit exceeded (5/hour)", "headers": { "Retry-After": { "$ref": "#/components/headers/Retry-After" } }, "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } } }, "503": { "description": "Server busy" }, "504": { "description": "PDF generation timed out" } } } }, "/v1/demo/markdown": { "post": { "tags": [ "Demo" ], "summary": "Convert Markdown to PDF (demo)", "description": "Public endpoint — no API key required. Rate limited to 5 requests per hour per IP.\nMarkdown is converted to HTML then rendered to PDF with a DocFast watermark.\n", "requestBody": { "required": true, "content": { "application/json": { "schema": { "allOf": [ { "type": "object", "required": [ "markdown" ], "properties": { "markdown": { "type": "string", "description": "Markdown content to convert", "example": "# Hello World\\n\\nThis is **bold** and *italic*." }, "css": { "type": "string", "description": "Optional CSS to inject" } } }, { "$ref": "#/components/schemas/PdfOptions" } ] } } } }, "responses": { "200": { "description": "Watermarked PDF document", "headers": { "X-RateLimit-Limit": { "$ref": "#/components/headers/X-RateLimit-Limit" }, "X-RateLimit-Remaining": { "$ref": "#/components/headers/X-RateLimit-Remaining" }, "X-RateLimit-Reset": { "$ref": "#/components/headers/X-RateLimit-Reset" } }, "content": { "application/pdf": { "schema": { "type": "string", "format": "binary" } } } }, "400": { "description": "Missing markdown field" }, "415": { "description": "Unsupported Content-Type" }, "429": { "description": "Demo rate limit exceeded (5/hour)", "headers": { "Retry-After": { "$ref": "#/components/headers/Retry-After" } } }, "503": { "description": "Server busy" }, "504": { "description": "PDF generation timed out" } } } }, "/v1/email-change": { "post": { "tags": [ "Account" ], "summary": "Request email change", "description": "Sends a 6-digit verification code to the new email address.\nRate limited to 3 requests per hour per API key.\n", "requestBody": { "required": true, "content": { "application/json": { "schema": { "type": "object", "required": [ "apiKey", "newEmail" ], "properties": { "apiKey": { "type": "string" }, "newEmail": { "type": "string", "format": "email" } } } } } }, "responses": { "200": { "description": "Verification code sent", "content": { "application/json": { "schema": { "type": "object", "properties": { "status": { "type": "string", "example": "verification_sent" }, "message": { "type": "string" } } } } } }, "400": { "description": "Missing or invalid fields" }, "403": { "description": "Invalid API key" }, "409": { "description": "Email already taken" }, "429": { "description": "Too many attempts" } } } }, "/v1/email-change/verify": { "post": { "tags": [ "Account" ], "summary": "Verify email change code", "description": "Verifies the 6-digit code and updates the account email.", "requestBody": { "required": true, "content": { "application/json": { "schema": { "type": "object", "required": [ "apiKey", "newEmail", "code" ], "properties": { "apiKey": { "type": "string" }, "newEmail": { "type": "string", "format": "email" }, "code": { "type": "string", "pattern": "^\\d{6}$" } } } } } }, "responses": { "200": { "description": "Email updated", "content": { "application/json": { "schema": { "type": "object", "properties": { "status": { "type": "string", "example": "ok" }, "newEmail": { "type": "string" } } } } } }, "400": { "description": "Missing fields or invalid code" }, "403": { "description": "Invalid API key" }, "410": { "description": "Code expired" }, "429": { "description": "Too many failed attempts" } } } }, "/health": { "get": { "tags": [ "System" ], "summary": "Health check", "description": "Returns service health status including database connectivity and browser pool stats.", "responses": { "200": { "description": "Service is healthy", "content": { "application/json": { "schema": { "type": "object", "properties": { "status": { "type": "string", "enum": [ "ok", "degraded" ] }, "version": { "type": "string", "example": "0.4.0" }, "database": { "type": "object", "properties": { "status": { "type": "string", "enum": [ "ok", "error" ] }, "version": { "type": "string", "example": "PostgreSQL 17.4" } } }, "pool": { "type": "object", "properties": { "size": { "type": "integer" }, "active": { "type": "integer" }, "available": { "type": "integer" }, "queueDepth": { "type": "integer" }, "pdfCount": { "type": "integer" }, "restarting": { "type": "boolean" }, "uptimeSeconds": { "type": "integer" } } } } } } } }, "503": { "description": "Service is degraded (database issue)" } } } }, "/v1/recover": { "post": { "tags": [ "Account" ], "summary": "Request API key recovery", "description": "Sends a 6-digit verification code to the email address if an account exists.\nResponse is always the same regardless of whether the email exists (to prevent enumeration).\nRate limited to 3 requests per hour.\n", "requestBody": { "required": true, "content": { "application/json": { "schema": { "type": "object", "required": [ "email" ], "properties": { "email": { "type": "string", "format": "email", "description": "Email address associated with the API key" } } } } } }, "responses": { "200": { "description": "Recovery code sent (or no-op if email not found)", "content": { "application/json": { "schema": { "type": "object", "properties": { "status": { "type": "string", "example": "recovery_sent" }, "message": { "type": "string" } } } } } }, "400": { "description": "Invalid email format" }, "429": { "description": "Too many recovery attempts" } } } }, "/v1/recover/verify": { "post": { "tags": [ "Account" ], "summary": "Verify recovery code and retrieve API key", "description": "Verifies the 6-digit code sent via email and returns the API key if valid. Code expires after 15 minutes.", "requestBody": { "required": true, "content": { "application/json": { "schema": { "type": "object", "required": [ "email", "code" ], "properties": { "email": { "type": "string", "format": "email" }, "code": { "type": "string", "pattern": "^\\d{6}$", "description": "6-digit verification code" } } } } } }, "responses": { "200": { "description": "API key recovered", "content": { "application/json": { "schema": { "type": "object", "properties": { "status": { "type": "string", "example": "recovered" }, "apiKey": { "type": "string", "description": "The recovered API key" }, "tier": { "type": "string", "enum": [ "free", "pro" ] } } } } } }, "400": { "description": "Invalid verification code or missing fields" }, "410": { "description": "Verification code expired" }, "429": { "description": "Too many failed attempts" } } } }, "/v1/templates": { "get": { "tags": [ "Templates" ], "summary": "List available templates", "description": "Returns a list of all built-in document templates with their required fields.", "security": [ { "BearerAuth": [] }, { "ApiKeyHeader": [] } ], "responses": { "200": { "description": "List of templates", "content": { "application/json": { "schema": { "type": "object", "properties": { "templates": { "type": "array", "items": { "type": "object", "properties": { "id": { "type": "string", "example": "invoice" }, "name": { "type": "string", "example": "Invoice" }, "description": { "type": "string" }, "fields": { "type": "array", "items": { "type": "object", "properties": { "name": { "type": "string" }, "required": { "type": "boolean" }, "description": { "type": "string" } } } } } } } } } } } }, "401": { "description": "Missing API key" }, "403": { "description": "Invalid API key" } } } }, "/v1/templates/{id}/render": { "post": { "tags": [ "Templates" ], "summary": "Render a template to PDF", "description": "Renders a built-in template with the provided data and returns a PDF.\nUse GET /v1/templates to see available templates and their required fields.\nSpecial fields: `_format` (page size), `_margin` (page margins), `_filename` (output filename).\n", "security": [ { "BearerAuth": [] }, { "ApiKeyHeader": [] } ], "parameters": [ { "in": "path", "name": "id", "required": true, "schema": { "type": "string" }, "description": "Template ID (e.g. \"invoice\", \"receipt\")" } ], "requestBody": { "required": true, "content": { "application/json": { "schema": { "type": "object", "properties": { "data": { "type": "object", "description": "Template data (fields depend on template). Can also be passed at root level." }, "_format": { "type": "string", "enum": [ "A4", "Letter", "Legal", "A3", "A5", "Tabloid" ], "default": "A4", "description": "Page size override" }, "_margin": { "type": "object", "properties": { "top": { "type": "string" }, "right": { "type": "string" }, "bottom": { "type": "string" }, "left": { "type": "string" } }, "description": "Page margin override" }, "_filename": { "type": "string", "description": "Custom output filename" } } } } } }, "responses": { "200": { "description": "PDF document", "content": { "application/pdf": { "schema": { "type": "string", "format": "binary" } } } }, "400": { "description": "Missing required template fields" }, "401": { "description": "Missing API key" }, "403": { "description": "Invalid API key" }, "404": { "description": "Template not found" }, "500": { "description": "Template rendering failed" } } } }, "/v1/signup/free": { "post": { "tags": [ "Account" ], "summary": "Free signup (discontinued)", "description": "Free accounts have been discontinued. Use the demo endpoint for testing\nor subscribe to Pro for production use.\n", "deprecated": true, "requestBody": { "content": { "application/json": { "schema": { "type": "object", "properties": { "email": { "type": "string", "format": "email" } } } } } }, "responses": { "410": { "description": "Free accounts discontinued", "content": { "application/json": { "schema": { "type": "object", "properties": { "error": { "type": "string", "example": "Free accounts have been discontinued." }, "demo_endpoint": { "type": "string", "example": "/v1/demo/html" }, "pro_url": { "type": "string", "example": "https://docfast.dev/#pricing" } } } } } } } } }, "/v1/usage": { "get": { "tags": [ "System" ], "summary": "Usage statistics (admin only)", "description": "Returns usage statistics for the authenticated user. Requires admin API key.", "security": [ { "BearerAuth": [] }, { "ApiKeyHeader": [] } ], "responses": { "200": { "description": "Usage statistics", "content": { "application/json": { "schema": { "type": "object", "additionalProperties": { "type": "object", "properties": { "count": { "type": "integer" }, "month": { "type": "string" } } } } } } }, "403": { "description": "Admin access required" }, "503": { "description": "Admin access not configured" } } } } } }