Some checks failed
Build & Deploy to Staging / Build & Deploy to Staging (push) Failing after 2m11s
- Update generate-openapi.mjs to include header components - Ensure public/openapi.json has rate limit headers - Update rate limits description in generation script
1350 lines
No EOL
40 KiB
JSON
1350 lines
No EOL
40 KiB
JSON
{
|
|
"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 <key>` or `X-API-Key: <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\nAll rate-limited endpoints return `X-RateLimit-Limit`, `X-RateLimit-Remaining`, and `X-RateLimit-Reset` headers. On `429`, a `Retry-After` header indicates seconds until the next allowed request.\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"
|
|
}
|
|
},
|
|
"headers": {
|
|
"X-RateLimit-Limit": {
|
|
"description": "The maximum number of requests allowed in the current time window",
|
|
"schema": {
|
|
"type": "integer",
|
|
"example": 30
|
|
}
|
|
},
|
|
"X-RateLimit-Remaining": {
|
|
"description": "The number of requests remaining in the current time window",
|
|
"schema": {
|
|
"type": "integer",
|
|
"example": 29
|
|
}
|
|
},
|
|
"X-RateLimit-Reset": {
|
|
"description": "Unix timestamp (seconds since epoch) when the rate limit window resets",
|
|
"schema": {
|
|
"type": "integer",
|
|
"example": 1679875200
|
|
}
|
|
},
|
|
"Retry-After": {
|
|
"description": "Number of seconds to wait before retrying the request (returned on 429 responses)",
|
|
"schema": {
|
|
"type": "integer",
|
|
"example": 60
|
|
}
|
|
}
|
|
},
|
|
"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": "<h1>Hello World</h1><p>My first PDF</p>"
|
|
},
|
|
"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": "<h1>Hello World</h1><p>My first PDF</p>"
|
|
},
|
|
"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"
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
} |