# TryDocify — instructions for AI coding assistants > Save this file in your project (for example as `docs/TRYDOCIFY.md`) and reference it from your > assistant's instructions (`CLAUDE.md`, `AGENTS.md`, `.cursor/rules`, `.github/copilot-instructions.md`). > Latest version: https://trydocify.com/trydocify.md · Human docs: https://trydocify.com/docs · OpenAPI: https://trydocify.com/openapi.json TryDocify is a REST API that turns a JSON document description into a typeset PDF. One request, no SDK: `POST https://api.trydocify.com/api/v1/generate` with the document as the JSON body returns the PDF bytes. ## Rules for integrating TryDocify 1. **Server-side only.** Call the API from backend code. Never put the API key in browser, mobile or other client-side code. 2. **Key from configuration.** Read the key from the `TRYDOCIFY_API_KEY` environment variable (or the app's secret store). Never hard-code or log it. Send it in the `X-API-Key` header. 3. **Test mode while developing.** Set `"test": true` in the document outside production. Test renders are free and watermarked "TEST" (200 per key per UTC day). Tie it to the environment, e.g. `test: process.env.NODE_ENV !== "production"`. 4. **Format values yourself.** Every text field is printed exactly as sent. Format money, dates and numbers in code before sending (e.g. `Intl.NumberFormat`, `f"{x:,.2f}"`, `ToString("C")`). 5. **Handle errors by `code`.** Non-2xx responses are RFC 9457 problem documents (`application/problem+json`) with a stable `code`. Branch on `code`, not on the message. 6. **Retry only 429 and 503**, waiting the seconds in the `Retry-After` header (max ~3 attempts). Other errors won't succeed on retry. 7. **Images inline only.** Images must be base64 data URIs; URLs are never fetched. 8. **Don't store PDFs unless needed.** Rendering is fast; generating on demand avoids stale files. ## Request ``` POST https://api.trydocify.com/api/v1/generate X-API-Key: Content-Type: application/json Accept: application/pdf (default; or application/json, see below) ``` Minimal body: ```json { "test": true, "title": "Hello, PDF", "blocks": [{ "type": "paragraph", "text": "This PDF was generated from JSON." }] } ``` ## Response - `200` with `Content-Type: application/pdf` and `Content-Disposition: attachment; filename=...`. Headers: `X-Page-Count`, `X-Render-Time-Ms`, `X-Credits-Remaining`; in test mode also `X-Docify-Test: true` and `X-Test-Renders-Remaining`. - With `Accept: application/json`: `{ "fileName", "contentType", "sizeBytes", "pageCount", "creditsRemaining", "renderTimeMs", "test", "testRendersRemaining", "data" }`, where `data` is the PDF as base64. - Each successful non-test render costs 1 credit. Validation errors and failed renders are never charged. ## Document schema Field names are camelCase. Enum values are case-insensitive. Where text is expected, numbers and booleans are accepted and printed as written. | Field | Type | Notes | |---|---|---| | `blocks` | Block[] | **Required.** 1–2,000 blocks, rendered top to bottom. | | `test` | boolean | Free, watermarked render. Default `false`. | | `title` | string | Large title on page 1; also PDF title metadata. Max 1,000 chars. | | `subtitle` | string | Muted line under the title. | | `fileName` | string | Download name; sanitized, `.pdf` appended. Default `document.pdf`. Max 120 chars. | | `pageSize` | `a3` \| `a4` \| `a5` \| `letter` \| `legal` | Default `a4`. | | `orientation` | `portrait` \| `landscape` | Default `portrait`. | | `header` | `{ "left": string, "right": string }` | Running header on every page. | | `footer` | `{ "text": string, "showPageNumbers": boolean }` | Running footer. `showPageNumbers` (Page X of Y) defaults to `true`. | | `theme` | `{ "accentColor", "textColor", "fontSize", "margin" }` | Colors `#RRGGBB`; `fontSize` 7–16 (default 10); `margin` 18–108 points (default 48; 72 pt = 1 inch). | | `info` | `{ "author", "subject", "keywords" }` | PDF metadata. | ### Blocks Every block has a `type`. | `type` | Fields | |---|---| | `heading` | `text` (required), `level` 1–3 (default 1) | | `paragraph` | `text` (required, `\n` keeps line breaks, max 50,000 chars), `align` (`left` \| `center` \| `right` \| `justify`), `bold`, `italic`, `muted` | | `list` | `items` (string[], 1–1,000), `ordered` (default `false`) | | `keyValue` | `items` (`[{ "key", "value" }]`, 1–500), `columns` 1–3 pairs per row (default 1) | | `table` | `columns` (`[{ "header", "align", "width" }]`, 1–24; `width` is relative 1–12), `rows` (each row must have exactly one cell per column; max 20,000 rows), `footer` (optional total rows; the last one is styled as the grand total), `striped` (default `true`) | | `note` | `text` (required), `title`, `tone` (`info` \| `success` \| `warning` \| `danger`) | | `image` | `src` (required: `data:image/png;base64,...`, also `jpeg`/`webp`, max 2 MB decoded, max 25 per document), `width` in points (8–2000, default full width), `align` | | `divider` | none | | `spacer` | `height` in points 1–400 (default 12) | | `pageBreak` | none | Tables flow across pages and repeat the header row. Total table cells per document: max 200,000. Request body: max 10 MB. ### Complete example (invoice) ```json { "test": true, "fileName": "invoice-1042.pdf", "title": "Invoice #1042", "subtitle": "Issued 3 November 2026 · Due 3 December 2026", "header": { "left": "Acme Studio", "right": "INV-1042" }, "footer": { "text": "Acme Studio · 221B Baker Street, London", "showPageNumbers": true }, "theme": { "accentColor": "#1D4ED8" }, "blocks": [ { "type": "keyValue", "columns": 2, "items": [ { "key": "Billed to", "value": "Globex Corporation" }, { "key": "Invoice no.", "value": "INV-1042" } ] }, { "type": "table", "columns": [ { "header": "Description", "width": 4 }, { "header": "Qty", "align": "right" }, { "header": "Amount", "align": "right", "width": 2 } ], "rows": [ ["Design system audit", 1, "$1,200.00"], ["Component library (React)", 3, "$2,550.00"] ], "footer": [ ["Subtotal", "", "$3,750.00"], ["VAT (20%)", "", "$750.00"], ["Total due", "", "$4,500.00"] ] }, { "type": "note", "tone": "info", "title": "Payment", "text": "Bank transfer, reference INV-1042." }, { "type": "divider" }, { "type": "paragraph", "text": "Thank you for your business.", "align": "center", "muted": true } ] } ``` ## Errors Problem document shape: ```json { "title": "The document payload is invalid.", "status": 400, "code": "validation_failed", "detail": "One or more fields failed validation. See 'errors' for details.", "traceId": "4bf92f3577b34da6a3ce929d0e0e4736", "errors": { "blocks[0].rows[0]": ["Each row must have exactly 2 cells (one per column)."] } } ``` | `code` | Status | What to do | |---|---|---| | `validation_failed` | 400 | Fix the document; `errors` maps JSON paths to messages. Not charged. | | `invalid_json` | 400 | The body isn't valid JSON or a value has the wrong type. | | `bad_request` | 400 | The request couldn't be processed (for example an empty body). | | `missing_api_key` / `invalid_api_key` | 401 | Send a valid key in `X-API-Key`. | | `revoked_api_key` | 401 | The key was revoked; use a current key. | | `quota_exceeded` | 402 | Monthly credits used up. Upgrade or wait for the reset. Test renders still work. | | `period_expired` | 402 | The renewal payment hasn't been confirmed. | | `subscription_inactive` | 403 | Subscription is past due or canceled. | | `payload_too_large` | 413 | Body over 10 MB. | | `unsupported_media_type` | 415 | Send `Content-Type: application/json`. | | `render_failed` | 422 | The layout engine rejected the document (e.g. an element taller than a page). Not charged. | | `rate_limited` | 429 | Retry after `Retry-After` seconds. Limit: 120 requests/minute per key, bursts of 30. | | `test_limit_reached` | 429 | Today's free test renders are used; resets 00:00 UTC. | | `renderer_busy` | 503 | Retry after `Retry-After` with backoff. Not charged. | | `internal_error` | 500 | Unexpected failure; log `traceId` and contact support@trydocify.com. | ## Other endpoints (all need `X-API-Key`) - `GET https://api.trydocify.com/api/v1/usage` — plan, credits (`limit`, `used`, `remaining`), billing period, `testMode` (daily limit and usage) and statistics. - `POST https://api.trydocify.com/api/v1/keys/rotate` — replaces the key; returns the new `apiKey` once. Plan and credits are kept. Update the stored secret immediately. - `POST https://api.trydocify.com/api/v1/billing/portal` — returns a short-lived `url` to manage the subscription (plan changes, payment method, invoices, cancellation). ## Reference implementation (Node.js 18+) ```js export async function generatePdf(document, { attempts = 3 } = {}) { for (let attempt = 1; ; attempt++) { const response = await fetch("https://api.trydocify.com/api/v1/generate", { method: "POST", headers: { "X-API-Key": process.env.TRYDOCIFY_API_KEY, "Content-Type": "application/json" }, body: JSON.stringify({ test: process.env.NODE_ENV !== "production", ...document }), }); if (response.ok) return Buffer.from(await response.arrayBuffer()); if ((response.status === 429 || response.status === 503) && attempt < attempts) { const seconds = Number(response.headers.get("Retry-After")) || attempt; await new Promise((resolve) => setTimeout(resolve, seconds * 1000)); continue; } const problem = await response.json(); throw new Error(`${response.status} ${problem.code}: ${problem.detail ?? problem.title} (traceId ${problem.traceId})`); } } ``` The same pattern in other languages: POST the JSON with the header, return the response bytes on success, retry 429/503 after `Retry-After`, raise with `code` otherwise. Language guides: https://trydocify.com/guides ## Guides - [How to generate a PDF from JSON with a REST API](https://trydocify.com/guides/generate-pdf-from-json): A step-by-step tutorial: send a JSON document to a REST API and get back a paginated PDF with tables, totals, headers and page numbers. Copy-paste cURL examples. - [Generate PDFs in .NET and ASP.NET Core](https://trydocify.com/guides/dotnet-pdf-generation): Create PDFs from C# with HttpClient and serve them from an ASP.NET Core endpoint, using a typed client and IHttpClientFactory. No native libraries or headless browser. - [Node.js PDF generation with a REST API](https://trydocify.com/guides/nodejs-pdf-generation): Generate PDFs in Node.js with fetch: a reusable helper with retries, and an Express route that streams a PDF to the browser. No Puppeteer, no Chromium. - [Python PDF generation with a REST API](https://trydocify.com/guides/python-pdf-generation): Generate PDFs in Python with requests: a small helper with error handling, and a Flask route that returns a PDF download. No system packages to install. - [Invoice PDF generation API](https://trydocify.com/use-cases/invoice-pdf-api): Turn invoice data into professional, multi-page PDF invoices: line items, tax, totals, payment details and a logo, mapped from your own data model in one function. - [PDF generation without Puppeteer: HTML to PDF vs JSON to PDF](https://trydocify.com/guides/pdf-generation-without-puppeteer): When a headless browser is the right tool for PDFs, when it isn't, and how JSON to PDF compares on pagination, deployment, speed and maintenance.