Generate

The heart of Transactional. Compile a document with your variables and get back a PDF URL. Typically called from your own app with an API token.

POST/v1/generateAPI token or cookie

Render a document to PDF

Compiles the document's Handlebars body with the supplied variables, renders it to PDF via Gotenberg, uploads to S3, and returns a signed URL. One generation credit is debited per call (refunded automatically on Gotenberg or S3 failure).

Request body

  • documentIdstring (uuid)requiredThe document's uuid. Use the value shown in the editor — never the internal numeric id.
  • variablesobjectHandlebars context. The shape mirrors the document's sample variables.
curl -X POST 'https://api.transactional.dev/v1/generate' \
  -H 'x-api-token: YOUR_API_TOKEN' \
  -H 'Content-Type: application/json' \
  -d '{
    "documentId": "1d8e5d56-1a4f-4b62-8c33-2d34a64b2f00",
    "variables": {
    "customer": { "name": "Acme Corp" },
    "invoice":  { "number": "INV-2026-0142", "total": 1280.50 }
}
  }'

Response

{
  "url": "https://files.transactional.dev/client/42/generated/1d8e5d56-1a4f-4b62-8c33-2d34a64b2f00/1716470000000.pdf",
  "documentId": "1d8e5d56-1a4f-4b62-8c33-2d34a64b2f00"
}

The url is signed and short-lived. Download or persist the PDF on your side immediately.

Errors

StatuserrorWhen
400invalid_document_iddocumentId is missing or not a uuid.
401UNAUTHORIZEDMissing or invalid x-api-token.
402quota_exceededGeneration credits exhausted. Top up or upgrade — the response carries { type: "generation" }.
404NOT_FOUNDdocumentId doesn't exist or isn't owned by this token.
503storage_unavailableS3 upload failed. The credit has been refunded — safe to retry.

Errors & conventions

Every non-2xx response is a JSON body shaped { "error": "ERROR_CODE", "message": "…" }. Branch on the stable error code — message is localized via Accept-Language and may change wording.

StatuserrorWhen
400VALIDATION_ERRORBody or query failed Zod validation. The response shape is { error, message }.
401UNAUTHORIZEDMissing or invalid x-api-token, or an expired cookie pair.
402quota_exceededOut of generation or AI credits. Response carries { error: "quota_exceeded", type, message }.
403FORBIDDENAuthenticated, but the credential type is wrong (e.g. API token on a cookie-only route).
404NOT_FOUNDResource doesn't exist or isn't owned by you.
429RATE_LIMIT_EXCEEDEDBack off and retry with exponential backoff.
500INTERNAL_ERRORUnexpected server error. Safe to retry once.
503SERVICE_UNAVAILABLEExternal dependency (Gotenberg / S3) is temporarily down. Retry with backoff.

Documents

Your Handlebars/HTML templates. The dashboard uses these endpoints to drive the editor; an API token can also list/inspect documents (handy for syncing template ids into your codebase). Editing is typically done in the UI.

GET/v1/documentsAPI token or cookie

List documents

Returns the user’s documents (non-archived), most recently updated first.

Query parameters

  • folderIdinteger | "root"Filter by folder. Pass root to list documents not in any folder. Omit for all.

Response

{
  "documents": [
    {
      "id": 12,
      "uuid": "1d8e5d56-1a4f-4b62-8c33-2d34a64b2f00",
      "name": "Invoice",
      "framework": "TAILWIND",
      "format": "A4",
      "landscape": false,
      "folderId": null,
      "previewUrl": "https://files.transactional.dev/client/42/previews/12.png",
      "previewUpdatedAt": "2026-05-22T13:50:00.000Z",
      "createdAt": "2026-01-04T11:20:00.000Z",
      "updatedAt": "2026-05-22T13:48:00.000Z",
      "lastUsedViaApi": "2026-05-22T13:51:42.000Z"
    }
  ]
}

Errors

StatuserrorWhen
400VALIDATION_ERRORRequest body or query did not pass validation.
401UNAUTHORIZEDMissing or invalid credentials.
404NOT_FOUNDResource doesn't exist or isn't owned by you.
500INTERNAL_ERRORUnexpected server error.
POST/v1/documentsAPI token or cookie

Create a document

Creates an empty template seeded with a small Hello-world body.

Request body

  • namestring (1–200)requiredDisplay name.

Request body example

{ "name": "Invoice" }

Response

{
  "id": 13,
  "uuid": "f4a09e72-1d1f-4c2a-9e64-…",
  "name": "Invoice",
  "framework": "TAILWIND",
  "format": "A4",
  "landscape": false,
  "body": "<!doctype html>\n<html>…</html>\n",
  "fonts": [],
  "variables": {},
  "archived": false,
  "createdAt": "2026-05-23T10:00:00.000Z",
  "updatedAt": "2026-05-23T10:00:00.000Z"
}

Errors

StatuserrorWhen
400VALIDATION_ERRORRequest body or query did not pass validation.
401UNAUTHORIZEDMissing or invalid credentials.
404NOT_FOUNDResource doesn't exist or isn't owned by you.
500INTERNAL_ERRORUnexpected server error.
GET/v1/documents/{id}API token or cookie

Get a document

Full payload — body, fonts, variables, and the page settings used at render time.

Path parameters

  • idintegerrequiredInternal document id (not the uuid).

Response

{
  "id": 12,
  "uuid": "1d8e5d56-1a4f-4b62-8c33-2d34a64b2f00",
  "name": "Invoice",
  "body": "<section class=\"p-12\">{{customer.name}}</section>",
  "framework": "TAILWIND",
  "format": "A4",
  "landscape": false,
  "fonts": [{ "name": "Inter Tight", "variants": ["400", "600"], "master": true }],
  "variables": { "customer": { "name": "Acme Corp" } },
  "archived": false
}

Errors

StatuserrorWhen
400VALIDATION_ERRORRequest body or query did not pass validation.
401UNAUTHORIZEDMissing or invalid credentials.
404NOT_FOUNDResource doesn't exist or isn't owned by you.
500INTERNAL_ERRORUnexpected server error.
PATCH/v1/documents/{id}API token or cookie

Update a document

Patch any subset of fields. Changing a renderable field (body, framework, format, landscape, fonts, variables) enqueues an off-thread preview render.

Path parameters

  • idintegerrequiredInternal document id.

Request body

  • namestring (1–200)
  • bodystringHandlebars-flavored HTML.
  • frameworkNONE | TAILWIND | BOOTSTRAP | UIKIT
  • formatA1 … A6
  • landscapeboolean
  • fontsFont[]Each font: { name, variants: string[], master?: boolean }.
  • variablesobjectSample variables used by the live preview.
  • archivedboolean
  • folderIdinteger | null

Request body example

{
  "name": "Invoice — v2",
  "format": "A4",
  "fonts": [{ "name": "Inter Tight", "variants": ["400","600"], "master": true }]
}

Response

/* The updated Document — same shape as GET /v1/documents/{id} */

Errors

StatuserrorWhen
400VALIDATION_ERRORRequest body or query did not pass validation.
401UNAUTHORIZEDMissing or invalid credentials.
404NOT_FOUNDResource doesn't exist or isn't owned by you.
500INTERNAL_ERRORUnexpected server error.
DELETE/v1/documents/{id}API token or cookie

Delete a document

Hard delete. Returns 204 with no body.

Path parameters

  • idintegerrequiredInternal document id.

Response

/* 204 No Content */

Errors

StatuserrorWhen
400VALIDATION_ERRORRequest body or query did not pass validation.
401UNAUTHORIZEDMissing or invalid credentials.
404NOT_FOUNDResource doesn't exist or isn't owned by you.
500INTERNAL_ERRORUnexpected server error.

Folders

Optional grouping for documents. Deleting a folder leaves its documents at root (folderId set to null).

GET/v1/foldersAPI token or cookie

List folders

Sorted alphabetically. Each row includes documentsCount.

Response

{
  "folders": [
    { "id": 1, "name": "Billing",   "emoji": "💸", "documentsCount": 4, "createdAt": "…", "updatedAt": "…" },
    { "id": 2, "name": "Reports",   "emoji": "📊", "documentsCount": 2, "createdAt": "…", "updatedAt": "…" }
  ]
}

Errors

StatuserrorWhen
400VALIDATION_ERRORRequest body or query did not pass validation.
401UNAUTHORIZEDMissing or invalid credentials.
404NOT_FOUNDResource doesn't exist or isn't owned by you.
500INTERNAL_ERRORUnexpected server error.
POST/v1/foldersAPI token or cookie

Create a folder

Request body

  • namestring (1–100)required
  • emojistring (≤ 8 chars)Optional decorator.

Request body example

{ "name": "Billing", "emoji": "💸" }

Response

{
  "id": 3,
  "name": "Billing",
  "emoji": "💸",
  "createdAt": "2026-05-23T10:00:00.000Z",
  "updatedAt": "2026-05-23T10:00:00.000Z"
}

Errors

StatuserrorWhen
400VALIDATION_ERRORRequest body or query did not pass validation.
401UNAUTHORIZEDMissing or invalid credentials.
404NOT_FOUNDResource doesn't exist or isn't owned by you.
500INTERNAL_ERRORUnexpected server error.
PATCH/v1/folders/{id}API token or cookie

Rename / re-emoji a folder

Path parameters

  • idintegerrequired

Request body

  • namestring (1–100)
  • emojistring | null (≤ 8 chars)

Request body example

{ "name": "Invoices", "emoji": "🧾" }

Response

/* The updated Folder */

Errors

StatuserrorWhen
400VALIDATION_ERRORRequest body or query did not pass validation.
401UNAUTHORIZEDMissing or invalid credentials.
404NOT_FOUNDResource doesn't exist or isn't owned by you.
500INTERNAL_ERRORUnexpected server error.
DELETE/v1/folders/{id}API token or cookie

Delete a folder

Documents survive at root (ON DELETE SET NULL on Document.folderId). Returns 204.

Path parameters

  • idintegerrequired

Response

/* 204 No Content */

Errors

StatuserrorWhen
400VALIDATION_ERRORRequest body or query did not pass validation.
401UNAUTHORIZEDMissing or invalid credentials.
404NOT_FOUNDResource doesn't exist or isn't owned by you.
500INTERNAL_ERRORUnexpected server error.

API keys

Long-lived tokens shaped tk_live_… used by your own app to call POST /v1/generate. The full value is shown exactly once at creation — we only store its SHA-256 hash. These endpoints accept the dashboard cookie only; an API token can't escalate by creating siblings.

GET/v1/api-keysDashboard cookie

List active API keys

Each row includes a 7-day usage sparkline (counts per day, oldest → newest).

Response

{
  "apiKeys": [
    {
      "id": 1,
      "name": "Production",
      "prefix": "tk_live_a1b2",
      "lastFour": "f9e0",
      "lastUsedAt": "2026-05-22T13:51:42.000Z",
      "createdAt": "2026-01-04T11:20:00.000Z",
      "usage7d": [12, 31, 28, 41, 39, 55, 18]
    }
  ]
}

Errors

StatuserrorWhen
400VALIDATION_ERRORRequest body or query did not pass validation.
401UNAUTHORIZEDMissing or invalid credentials.
404NOT_FOUNDResource doesn't exist or isn't owned by you.
500INTERNAL_ERRORUnexpected server error.
POST/v1/api-keysDashboard cookie

Create an API key

token is returned ONCE. Surface it to the user immediately and never store it server-side.

Request body

  • namestring (1–100)requiredHuman label.

Request body example

{ "name": "Production" }

Response

{
  "id": 2,
  "name": "Production",
  "prefix": "tk_live_a1b2",
  "lastFour": "f9e0",
  "lastUsedAt": null,
  "createdAt": "2026-05-23T10:00:00.000Z",
  "token":  "tk_live_a1b2c3d4e5f6…f9e0"
}

Errors

StatuserrorWhen
400VALIDATION_ERRORRequest body or query did not pass validation.
401UNAUTHORIZEDMissing or invalid credentials.
404NOT_FOUNDResource doesn't exist or isn't owned by you.
500INTERNAL_ERRORUnexpected server error.
DELETE/v1/api-keys/{id}Dashboard cookie

Revoke an API key

Soft delete — sets revokedAt. Subsequent calls with this token return 401.

Path parameters

  • idintegerrequired

Response

/* 204 No Content */

Errors

StatuserrorWhen
400VALIDATION_ERRORRequest body or query did not pass validation.
401UNAUTHORIZEDMissing or invalid credentials.
404NOT_FOUNDResource doesn't exist or isn't owned by you.
500INTERNAL_ERRORUnexpected server error.