Integrate makesPDF into your app
Drop-in “AI builds PDF templates in my app” without rebuilding the authoring loop. Three copy-paste recipes, each under 60 seconds. Everything below couples over plain REST with your own API key — bring your own LLM key, pay us only for the pixels.
Which recipe do you need?
| You want… | Use | Lives |
|---|---|---|
| Production PDFs from your backend with data you already have | Recipe 1: backend render | This page |
| Your existing agent (Claude Code, Cursor, Desktop) authoring templates with schema-validated tools, zero code | Recipe 2: hosted MCP | This page + MCP server |
| Authoring tools embedded in your own app next to your own model calls | Recipe 3: Vercel AI SDK tools (REST shim) | Paste today; ships as @makespdf/sdk next |
A prebuilt chat widget (<MakespdfChat>) is deferred until integrators ask for it — the three recipes above cover the backend, agent, and in-app cases without one. Full backend detail (error table, retries, versioning, delivery patterns) lives in the application-integration skill.
Recipe 1: render production PDFs from your backend~60 seconds
Save the template once, render it many times with different data. Billing is 1 credit per 10 pages on success; every failure path deducts zero. Rate limit: 2,000 renders/hour per caller.
Step 1: save the template (one time)
# Write your DSL to a file, then save it and capture the templateId
jq -n --rawfile dsl template.js --arg name "Invoice v1" \
'{ dsl: $dsl, name: $name }' |
curl -sX POST https://makespdf.com/api/v1/templates \
-H "Authorization: Bearer $MAKESPDF_API_KEY" \
-H "Content-Type: application/json" \
--data-binary @-
# -> { "templateId": "11111111-2222-...", "name": "Invoice v1", "createdAt": ... }
# Store it as config, e.g. MAKESPDF_INVOICE_TEMPLATE_ID=11111111-2222-...Step 2: render with real data (every request)
export async function renderInvoice(data: unknown): Promise<Uint8Array> {
const res = await fetch("https://makespdf.com/api/v1/render", {
method: "POST",
headers: {
Authorization: `Bearer ${process.env.MAKESPDF_API_KEY}`,
"Content-Type": "application/json",
},
body: JSON.stringify({
templateId: process.env.MAKESPDF_INVOICE_TEMPLATE_ID,
data,
}),
});
if (!res.ok) {
throw new Error(`makesPDF ${res.status}: ${await res.text()}`);
}
return new Uint8Array(await res.arrayBuffer());
}Only retry on 429 or 5xx (max 3 attempts, respect Retry-After); never blanket-retry 400/401/402/404. 402 means out of credits — top up at Settings → Billing. Full error table and retry helper in the integration skill.
Recipe 2: give your agent template powers via hosted MCP~60 seconds
No code. Register the hosted MCP server once and the agent gets schema-validated tools for validating, previewing, and saving DSL templates — plus the skill file as a fetchable resource. Authoring tools are free; only render_template (and render_markdown) are billed, at the same 1 credit per 10 pages as REST.
Step 1: create an API key
Create one at Settings → API Keys. The MCP endpoint uses the same Bearer auth as every other /api/v1/* endpoint.
Step 2: register the server (Claude Code)
claude mcp add --transport http makespdf \
https://makespdf.com/api/v1/mcp \
--header "Authorization: Bearer mpdf_your_api_key"Claude Desktop and Cursor use the same URL and header in their JSON config — see MCP server for the per-client files.
Step 3: verify, then prompt
curl -X POST https://makespdf.com/api/v1/mcp \
-H "Authorization: Bearer mpdf_your_api_key" \
-H "Content-Type: application/json" \
-d '{"jsonrpc":"2.0","id":1,"method":"tools/list"}'
# -> JSON listing all ten tools (validate_dsl, render_dsl_preview, ...)Create an invoice template for Acme Corp with 3 line items, save it, and
show me the preview PDF.The agent reads the skill resource, drafts DSL, calls validate_dsl, fixes issues, calls render_dsl_preview (free, watermarked), then save_template. Discovery methods answer without a key; tools/call needs the Bearer token.
Recipe 3: embed authoring tools in your app (Vercel AI SDK)~60 seconds
For custom apps that already call an LLM: wrap the same REST endpoints as tools your model can call, and fetch the skill live with a vendored fallback. REST-only coupling — no shared imports, no proxy LLM, no billing passthrough. BYOK only: the integrator supplies their own model key; MAKESPDF_API_KEY pays for renders alone. This shim is the inline precursor to @makespdf/sdk (MakespdfClient + tools() + getSkill(), OIDC-published) — same shape, pasteable today.
Step 1: live skill fetch with offline fallback
// skill.ts — fetch the canonical skill live; fall back to the vendored copy.
const SKILL_URL = "https://makespdf.com/skills/pdf-template-author.md";
let cached: string | null = null;
export async function getSkill(fallback?: string): Promise<string> {
if (cached) return cached;
try {
const res = await fetch(SKILL_URL);
if (!res.ok) throw new Error(`skill fetch ${res.status}`);
cached = await res.text();
return cached;
} catch (err) {
// Offline / deploy-frozen fallback: vendor once with
// curl -o pdf-template-author.md https://makespdf.com/skills/pdf-template-author.md
// and pass its contents as `fallback`.
if (fallback) {
cached = fallback;
return cached;
}
throw err;
}
}Step 2: REST-backed tools for your model
// makespdf-tools.ts — REST-only tools() shim (BYOK; no proxy, no billing passthrough).
import { tool } from "ai";
import { z } from "zod";
const dataSchema = z.record(z.string(), z.unknown()).optional();
export function makespdfTools(opts: { apiKey: string; baseUrl?: string }) {
const base = (opts.baseUrl ?? "https://makespdf.com").replace(/\/$/, "");
const headers = {
Authorization: `Bearer ${opts.apiKey}`,
"Content-Type": "application/json",
};
const post = async (path: string, body: unknown) => {
const res = await fetch(`${base}${path}`, {
method: "POST",
headers,
body: JSON.stringify(body),
});
if (!res.ok) throw new Error(`makesPDF ${res.status}: ${await res.text()}`);
return res;
};
return {
validate_dsl: tool({
description: "Catalog + accessibility check for a DSL script. Cheap, no render.",
inputSchema: z.object({ dsl: z.string(), data: dataSchema }),
execute: async ({ dsl, data }) =>
post("/api/v1/preview/validate", { dsl, data }).then((r) => r.json()),
}),
render_dsl_preview: tool({
description: "Free watermarked preview PDF. Returns { pageCount, pdfBase64 }.",
inputSchema: z.object({ dsl: z.string(), data: dataSchema }),
execute: async ({ dsl, data }) => {
const res = await post("/api/v1/preview", { dsl, data });
const buf = Buffer.from(await res.arrayBuffer());
return {
pageCount: Number(res.headers.get("x-pages") ?? 0),
pdfBase64: buf.toString("base64"),
};
},
}),
render_template: tool({
description: "BILLED (1 credit / 10 pages). Render a saved templateId to a real PDF.",
inputSchema: z.object({ templateId: z.string(), data: dataSchema }),
execute: async ({ templateId, data }) => {
const res = await post("/api/v1/render", { templateId, data });
const buf = Buffer.from(await res.arrayBuffer());
return {
pageCount: Number(res.headers.get("x-pages") ?? 0),
creditsDeducted: Number(res.headers.get("x-credits-deducted") ?? 0),
pdfBase64: buf.toString("base64"),
};
},
}),
};
}Step 3: wire it next to your model call
import { generateText } from "ai";
import { getSkill } from "./skill";
import { makespdfTools } from "./makespdf-tools";
const skill = await getSkill(); // or getSkill(vendoredFallbackText)
const { text } = await generateText({
model: YOUR_MODEL, // your key, your account — makesPDF never proxies LLM calls
system: skill + "\n\n" + "Use validate_dsl before every preview; only call render_template when the layout is confirmed.",
tools: makespdfTools({ apiKey: process.env.MAKESPDF_API_KEY! }),
prompt: "Create an invoice template for Acme Corp with 3 line items.",
});What’s next
- Application-integration skill — the full backend reference this page compresses: error table, retries, versioning, delivery patterns, per-language snippets.
- MCP server — the ten hosted tools, per-client registration files, and verify steps.
- Bring your own AI — skill-only flows and CLI-based authoring when MCP isn’t available.
- API Reference — the REST endpoints every recipe above wraps.