<!-- SKILL-VERSION: pdf-api 3ed568e@2026-06-05T18:19:05+10:00 -->
> **Skill version:** `3ed568e@2026-06-05T18:19:05+10:00` (`pdf-api`).
> If you have a cached copy of this skill with a different version token,
> discard it and use this copy. Re-fetch this URL at the start of each
> session to detect updates.

# makesPDF — API reference

Companion to [`pdf-template-author.md`](./pdf-template-author.md). Once
you've authored a DSL script, this is how you actually call the engine.
Three endpoints:

- **`POST /api/v1/preview`** — render inline DSL with data. Free, draft
  watermark + filler text. Use during authoring.
- **`POST /api/v1/render`** — render a saved template by id. Billed (1
  credit / 10 pages). Use in production.
- **`POST /api/v1/preview/validate`** — catalog + accessibility check
  without rendering. Cheap, do this before every preview.

For getting a Bearer token in the first place, see
[`/skills/pdf-auth.md`](https://makespdf.com/skills/pdf-auth.md).
For uploading custom fonts to your account, see
[`/skills/pdf-fonts.md`](https://makespdf.com/skills/pdf-fonts.md).

> **Quick path for Markdown.** If your input is already GitHub-flavoured
> Markdown, use `POST /api/v1/md` instead of authoring DSL — see
> [`pdf-template-author.md`](./pdf-template-author.md) §"Markdown → PDF".
> `/md` also has an anonymous-renders carve-out: when the operator
> enables `promo:md_anonymous_enabled`, the endpoint accepts requests
> with no Authorization header at all, subject to per-IP rate limits
> (60/hour, 200/day, 20 pages per render). Authenticate anyway for
> higher limits and artifact capture.

---

## Preview API

Render your template by sending it to the preview endpoint. The `dsl` field
is **your DSL script serialized as a JSON string** — newlines escaped as
`\n`, double quotes escaped as `\"`. `data` is the JSON object your
`{{variables}}` resolve against; omit it and the engine falls back to the
`sampleData` declared inside the script.

```
POST /api/v1/preview
Authorization: Bearer <your access_token>
Content-Type: application/json

{
  "dsl": "const template = doc({ size: \"A4\" }, page(col(s(\"Hello {{name}}\"))));\nconst sampleData = { name: \"World\" };",
  "data": { "name": "Ada" }
}
```

Runnable curl with a tiny template inline (multi-line DSL goes in a file —
see §Sending DSL from a shell):

```bash
curl -X POST "$API/api/v1/preview" \
  -H "Authorization: Bearer $MAKESPDF_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"dsl":"const template = doc({ size: \"A4\" }, page(col(s(\"Hello {{name}}\"))));\nconst sampleData = { name: \"World\" };","data":{"name":"Ada"}}' \
  -o hello.pdf
```

Response: PDF binary (default) or JSON metadata (with
`Accept: application/json` header).

Responses on the free / dev tier include a small `makespdf.com` attribution
line in the footer area. Paid tiers remove it.

### `/preview` is draft-only

To stop callers treating preview output as a final deliverable, every
preview render (a) replaces string values in `data` with length-preserving
filler — numbers, booleans, ISO-like dates, and numeric-looking strings
pass through untouched, and hardcoded labels inside the DSL are always
verbatim — and (b) bakes a diagonal "PREVIEW — NOT FOR USE" overlay into
the content stream. Layout breakages still surface at realistic widths so
authoring feedback stays useful, but the PDF is not usable as a
production artifact. When you want real data in the output, save the
template (`POST /api/v1/templates`) and call `/render` against it.

### Custom fonts (`fonts` field)

Both `/preview` and `/render` accept an optional `fonts` array to register
additional typefaces for a single request. Per-request fonts are fetched
from `https://` URLs on every call — wasteful if you render the same
template many times. For brand fonts and CJK, prefer account-level
uploads via `POST /api/v1/fonts` instead.

Full font workflow (per-request `fonts`, account-level uploads, Google
Fonts mapping, variable-font handling): see
[`/skills/pdf-fonts.md`](https://makespdf.com/skills/pdf-fonts.md).

Quick reference:

```jsonc
{
  "dsl": "…",
  "data": { … },
  "fonts": [
    {
      "family": "Brand",
      "variants": [
        { "src": "https://cdn.example.com/Brand-Regular.ttf" },
        { "src": "https://cdn.example.com/Brand-Bold.ttf", "weight": "bold" }
      ]
    }
  ]
}
```

Resolver precedence (highest wins): per-request `fonts` →
account-level `/api/v1/fonts` rows → embedded (`Inter`, `NotoSans`,
`Cousine`, `NotoSymbols2`).

### Sending DSL from a shell

The `dsl` field is a JSON string, so embedding a multi-line DSL script
inline is awkward (template-literal backticks, escaped newlines, `{{…}}`
syntax that linters misread as JS interpolation). Write the DSL to a file,
then build the JSON payload with `jq`:

```bash
cat > template.js <<'EOF'
const cols = [["Description", "50%"], ["Qty", "20%", "right"], ["Amount", "30%", "right"]];
const template = doc({ size: "A4" }, minHdr("Invoice", "Acme Corp") /* … */);
const sampleData = { /* … */ };
EOF

jq -n --rawfile dsl template.js --argjson data "$(cat data.json)" \
  '{ dsl: $dsl, data: $data }' |
  curl -sX POST "${API}/api/v1/preview" \
    -H "Authorization: Bearer $MAKESPDF_API_KEY" \
    -H "Content-Type: application/json" \
    --data-binary @- -o invoice.pdf
```

`jq -n --rawfile` avoids manually escaping newlines and quotes. If you
don't have a separate `data.json`, omit the `--argjson data` and drop
`data: $data` — the engine will fall back to the script's `sampleData`.

---

## Render API

Once a template is stable, save it once and render it many times with
different data. Two steps:

**1. Save the template** (free, `POST /api/v1/templates`):

```bash
jq -n --rawfile dsl template.js --arg name "Acme invoice" \
  '{ dsl: $dsl, name: $name }' |
  curl -sX POST "$API/api/v1/templates" \
    -H "Authorization: Bearer $MAKESPDF_API_KEY" \
    -H "Content-Type: application/json" \
    --data-binary @-
# → { "templateId": "…uuid…", "name": "Acme invoice", "createdAt": … }
```

**2. Render with data** (billed, `POST /api/v1/render`):

```
POST /api/v1/render
Authorization: Bearer <your access_token>
Content-Type: application/json

{
  "templateId": "11111111-2222-3333-4444-555555555555",
  "data": { "invoiceNumber": "INV-042", "items": [ … ] }
}
```

```bash
curl -X POST "$API/api/v1/render" \
  -H "Authorization: Bearer $MAKESPDF_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"templateId":"…uuid…","data":{ … }}' \
  -o invoice.pdf
```

Response: PDF binary (default) or JSON metadata (with
`Accept: application/json`). Headers include `X-Pages`,
`X-Credits-Deducted`, `X-Credits-Remaining`.

Error responses:

- **400** — malformed JSON, missing `templateId`, non-UUID `templateId`,
  or non-object `data`.
- **401** — no valid Bearer token / session cookie.
- **402** — credits exhausted. Upgrade or top up at `/settings/billing`.
- **404** — `templateId` is unknown **or** not owned by the caller. The
  response is identical in both cases by design — the endpoint never
  confirms existence of other users' templates.
- **429** — rate limit (200 renders/hour per caller).

**Billing:** 1 credit per 10 pages on success. **Every failure path
deducts zero credits** — validation errors, 404s, 402s, and render
exceptions all leave the balance untouched.

---

## Validation API

Before rendering, validate your template for structure and accessibility issues:

```
POST /api/v1/preview/validate
Authorization: Bearer <your access_token>
Content-Type: application/json

{
  "dsl": "<your DSL script as a string>"
}
```

Response:

```json
{
  "valid": true,
  "issues": [
    {
      "severity": "warning",
      "message": "Image missing alt text ...",
      "path": "kids[0].kids[2]",
      "rule": "a11y-missing-alt"
    }
  ],
  "summary": { "errors": 0, "warnings": 1 }
}
```

This is a cheap pre-flight check (no rendering). It catches:

- **Catalog issues:** unknown tags, invalid nesting, unknown style
  properties, missing row widths.
- **Accessibility issues:** images missing alt text (required for
  PDF/UA-1).

**Best practice:** always call `/api/v1/preview/validate` before
`/api/v1/preview`. If issues are found, fix them and re-validate.
Report any warnings to the user so they can make informed decisions
about accessibility.
