Slipmint API docs
Slipmint turns JSON (or your own HTML) into a PDF. Everything on this page describes code that exists today. Anything else is marked planned.
Status
- The API code, templates, validation and quota counting are built and covered by automated smoke tests.
- PDF rendering is delegated to a self-hosted Gotenberg 8 (Chromium) renderer on Google Cloud Run. The API isn't open for public signups. Free API keys go out by invite to waitlist members. If no renderer is configured,
POST /v1/pdfvalidates your input and returns501. - No service-level agreement or uptime commitment is offered.
Quickstart
Base URL: https://slipmint-api.mike-tusa.workers.dev
curl
BASE=https://slipmint-api.mike-tusa.workers.dev
export SLIPMINT_API_KEY=YOUR_API_KEY # free keys go out by invite to waitlist members
curl -X POST "$BASE/v1/pdf" \
-H "Authorization: Bearer $SLIPMINT_API_KEY" \
-H "Content-Type: application/json" \
-o invoice.pdf \
-d '{"template_id":"invoice","data":{"company_name":"Northwind Studio","brand_color":"#0f766e",
"invoice_number":"INV-1042","issue_date":"2026-09-25","customer_name":"Acme Corp",
"currency":"USD","tax_rate":8.25,
"items":[{"description":"Website redesign","quantity":1,"unit_price":4500}]}}'
# Your own HTML instead of a template:
curl -X POST "$BASE/v1/pdf" -H "Authorization: Bearer $SLIPMINT_API_KEY" \
-H "Content-Type: application/json" -o page.pdf \
-d '{"html":"<h1>Hello</h1><style>@page{size:Letter}</style>","filename":"page.pdf"}'
# Check the rendered HTML without using a document from your quota:
curl -X POST "$BASE/v1/pdf?preview=html" -H "Authorization: Bearer $SLIPMINT_API_KEY" \
-H "Content-Type: application/json" -d @invoice.json -o preview.html
JavaScript (Node 18+, ESM)
import { writeFile } from "node:fs/promises";
const BASE = "https://slipmint-api.mike-tusa.workers.dev";
const res = await fetch(`${BASE}/v1/pdf`, {
method: "POST",
headers: { Authorization: `Bearer ${process.env.SLIPMINT_API_KEY}`, "Content-Type": "application/json" },
body: JSON.stringify({
template_id: "certificate",
data: {
recipient_name: "Ada Lovelace", course_name: "Intro to Analytics",
issued_date: "September 25, 2026", issuer_name: "Example Academy",
brand_color: "#b45309", font: "EB Garamond", page_size: "Letter",
},
}),
});
if (!res.ok) throw new Error(`${res.status} ${await res.text()}`);
console.log("usage:", res.headers.get("X-Slipmint-Usage"));
await writeFile("certificate.pdf", Buffer.from(await res.arrayBuffer()));
Python (requests)
import os, requests
BASE = "https://slipmint-api.mike-tusa.workers.dev"
resp = requests.post(
f"{BASE}/v1/pdf",
headers={"Authorization": f"Bearer {os.environ['SLIPMINT_API_KEY']}"},
json={
"template_id": "report",
"data": {
"company_name": "Brightline Agency", "report_title": "Monthly Report",
"period": "September 2026", "brand_color": "#1d4ed8",
"kpi_1_label": "Sessions", "kpi_1_value": "48,210",
"summary": "First paragraph.\n\nSecond paragraph.",
"rows": [{"label": "Organic", "value": "22,140"}, {"label": "Direct", "value": "12,030"}],
},
},
timeout=60,
)
if resp.status_code != 200:
raise SystemExit(f"{resp.status_code}: {resp.text}")
open("report.pdf", "wb").write(resp.content)
Authentication
Every /v1/* request needs Authorization: Bearer <api key>. Keys are stored only as SHA-256 hashes, so a lost key can't be recovered and has to be replaced. The pages /, /docs, /gallery/* and /health are public.
POST /v1/pdf
JSON body with exactly one of:
| Field | Type | Notes |
|---|---|---|
template_id + data | string + object | One of invoice, receipt, report, certificate. data is validated against the template tokens below. |
html | string, max 2 MB | A full HTML document. Use CSS @page { size: A4 } or Letter to set the paper size. |
filename | string (optional) | Used in Content-Disposition. Letters, digits, . _ -; max 80. |
webhook_url | (planned) | Async render with a webhook. Returns 501 for now. |
Response: 200 application/pdf with the header X-Slipmint-Usage: <used>/<included>. With ?preview=html you get 200 text/html instead, and it isn't counted.
Templates & tokens
All text values are HTML-escaped before they go into the template, so you can't inject markup through data. GET /v1/templates returns this same information as JSON, including sample data.
Invoice invoice (portrait)
Branded invoice with line items, computed subtotal/tax/total, status badge, payment details and notes. Preview
| Token | Type | Default | Notes |
|---|---|---|---|
logo_url | url | https:// or data:image/ URL. Hidden when empty. | |
brand_color | color | #2563eb | Hex color. Text on brand-colored areas switches to black/white automatically for contrast. |
font | font | Helvetica | Font family name installed on the renderer (letters, digits, spaces, hyphens; max 40). |
page_size | page_size | A4 | A4 or Letter. |
company_name required | text | ||
company_address | longtext | ||
company_email | text | ||
invoice_number required | text | ||
issue_date required | text | ||
due_date | text | ||
status | text | Badge text, e.g. DUE, PAID, OVERDUE. Hidden when empty. | |
customer_name required | text | ||
customer_address | longtext | ||
customer_email | text | ||
reference | text | PO number / project reference. | |
items | items | [{description, quantity, unit_price}]; subtotal/tax/total are computed. | |
currency | currency | USD | ISO 4217 code, e.g. USD, EUR, GBP. |
locale | text | en-US | BCP 47 locale used for number/currency formatting. |
tax_rate | number | Percent, e.g. 8.25. Omit for no tax line. | |
tax_label | text | Tax | Label for the tax line. |
payment_details | longtext | Bank / payment link instructions. | |
notes | longtext | ||
thank_you_note | text |
Receipt receipt (portrait)
Payment receipt card with amount paid, payment method, line items and totals. Preview
| Token | Type | Default | Notes |
|---|---|---|---|
logo_url | url | https:// or data:image/ URL. Hidden when empty. | |
brand_color | color | #2563eb | Hex color. Text on brand-colored areas switches to black/white automatically for contrast. |
font | font | Helvetica | Font family name installed on the renderer (letters, digits, spaces, hyphens; max 40). |
page_size | page_size | A4 | A4 or Letter. |
company_name required | text | ||
company_email | text | ||
receipt_number required | text | ||
paid_at required | text | ||
payment_method | text | Card | |
customer_name | text | ||
items | items | [{description, quantity, unit_price}]; subtotal/tax/total are computed. | |
currency | currency | USD | ISO 4217 code, e.g. USD, EUR, GBP. |
locale | text | en-US | BCP 47 locale used for number/currency formatting. |
tax_rate | number | Percent, e.g. 8.25. Omit for no tax line. | |
tax_label | text | Tax | Label for the tax line. |
thank_you_note | text | Thanks for your payment. |
Simple report report (portrait)
One- or two-page report: branded cover band, three KPIs, summary paragraphs and a data table. Preview
| Token | Type | Default | Notes |
|---|---|---|---|
logo_url | url | https:// or data:image/ URL. Hidden when empty. | |
brand_color | color | #2563eb | Hex color. Text on brand-colored areas switches to black/white automatically for contrast. |
font | font | Helvetica | Font family name installed on the renderer (letters, digits, spaces, hyphens; max 40). |
page_size | page_size | A4 | A4 or Letter. |
company_name required | text | ||
report_title required | text | ||
period | text | ||
prepared_for | text | ||
generated_at | text | ||
kpi_1_label | text | ||
kpi_1_value | text | ||
kpi_2_label | text | ||
kpi_2_value | text | ||
kpi_3_label | text | ||
kpi_3_value | text | ||
summary | longtext | Plain text; blank lines start new paragraphs. | |
table_title | text | Details | |
rows | rows | [{label, value, note?}] rendered as the data table. | |
footer_note | text |
Certificate certificate (landscape)
Landscape certificate of completion with double brand border, seal and signature lines. Preview
| Token | Type | Default | Notes |
|---|---|---|---|
logo_url | url | https:// or data:image/ URL. Hidden when empty. | |
brand_color | color | #2563eb | Hex color. Text on brand-colored areas switches to black/white automatically for contrast. |
font | font | EB Garamond | Font family. Default: bundled EB Garamond (SIL OFL); other names must be installed on the renderer. |
page_size | page_size | A4 | A4 or Letter. |
certificate_title | text | Certificate of Completion | |
recipient_name required | text | ||
course_name required | text | ||
description | text | ||
issued_date required | text | ||
issuer_name required | text | ||
signer_name | text | ||
signer_title | text | ||
seal_text | text | Short text in the seal, e.g. the year. Max ~8 chars looks best. | |
certificate_id | text |
Template gallery
Live previews rendered with sample data by the same code path as the PDF (open one full-size to print-preview it). Every name and number is made up.
Query options: ?page_size=A4|Letter and ?brand_color=7c3aed (hex without #).
Branding & page size
brand_color: a hex color used for headers, accents, table heads and the certificate border. Text placed on brand-colored areas switches between black and white automatically for WCAG contrast.logo_url: anhttps://ordata:image/URL. If it's empty, the logo slot is hidden.font: the font family name. The certificate defaults to EB Garamond, which is bundled (SIL Open Font License) and embedded in the document, so it always renders; you can also use it on other templates with"font": "EB Garamond". Other fonts have to be installed on the renderer: the Gotenberg 8 image ships Noto, DejaVu, Liberation (metric-compatible with Arial/Helvetica/Times), Carlito and Caladea. Unknown names fall back to the template's sans-serif or serif stack.page_size:A4(default) orLetter. It's applied through CSS@page, and the renderer is called withpreferCssPageSize=true. Certificates are landscape.
Other endpoints
| Route | Auth | Returns |
|---|---|---|
GET /health | no | Service status, whether a renderer is configured, and the store type. |
GET /v1/templates | yes | Templates, tokens and sample data. |
GET /v1/usage | yes | Documents used this calendar month (UTC), plus included, overage and remaining. |
GET /gallery/:id | no | HTML preview with sample data. |
Errors
| Status | error | Meaning |
|---|---|---|
| 400 | invalid_json / invalid_body | The body isn't a JSON object. |
| 401 | missing_api_key / invalid_api_key | Check the Authorization header. |
| 413 | payload_too_large | html is over 2 MB. |
| 422 | validation_failed | details[] lists every problem found. |
| 422 | blocked_url | The HTML or logo_url references a private, loopback, link-local or cloud-metadata address (for example localhost, 10.x, 169.254.169.254). Only public URLs can be fetched. |
| 404 | not_found | Unknown route, including unknown /v1/… paths. |
| 429 | quota_exceeded | You've hit your plan's hard monthly cap (Free plan). |
| 501 | not_implemented | No renderer is configured, or you used a planned feature (webhook_url). |
| 502 | renderer_unreachable / render_failed | The renderer failed. The document isn't counted. |
Limits & quotas
- A document is counted only when a PDF is returned successfully. The count is reserved atomically before rendering and refunded if the render fails.
- Usage resets on the 1st of each month (UTC).
- Maximum
htmlsize is 2 MB, anditems/rowsare capped at 200 entries.
Pricing (planned)
| Plan | Price | Included | Beyond included |
|---|---|---|---|
| Free | $0 | 50 documents / month | None (hard cap, returns 429) |
| Indie | $15 / month | 1,000 documents / month | Then $0.03 per extra document |
| Agency | $49 / month | 5,000 documents / month, up to 25 brands (not enforced yet) | Then $0.03 per extra document |
No payments are taken yet. Brand limits aren't enforced in the API yet, because branding is sent per request.
Privacy & retention
Summary (the full policy is at /privacy):
- Documents are processed transiently and streamed back in the response. They aren't retained in any database or file storage.
- The API runs on Cloudflare Workers. The renderer runs on Google Cloud Run (us-central1, USA), where Gotenberg's temporary working files live in Cloud Run's in-memory filesystem and are removed when the request finishes.
- We store a SHA-256 hash of each API key, its label and plan, and a document count per key per month.
- We never log request bodies, rendered output or API keys. The hosting providers record standard request metadata (time, IP address, path, status).
- Requests that reference private, loopback, link-local or cloud-metadata addresses are rejected where detected; the renderer blocks private IPs at fetch time. Report security issues: digitalpromohub.support+slipmint@gmail.com.
API keys
Free API keys (50 documents per month) go out by invite to waitlist members. Join the waitlist on the home page and we'll send you one launch invite, in batches, with a personal, single-use link that creates your key. The link expires after 7 days, and the key is shown only once. See privacy §6 and §6a for what's stored and how to delete it.
Use of the API is subject to the beta terms.