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. The URL is public and permanent — deleting the file is an explicit call.

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 public 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 public and permanent — it is not signed and never expires. Anyone holding it can download the PDF, and it stays served until you remove it with DELETE /v1/generations/file.

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.
DELETE/v1/generations/fileAPI token or cookie

Delete a generated PDF

Removes a PDF produced by /v1/generate from storage and invalidates it on the CDN. Only files generated by your own account can be deleted. No credit is consumed and the source document is untouched. Returns 204 with no body.

Request body

  • pathstringrequiredThe url returned by /v1/generate, or its absolute storage path.

Request body example

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

Response

/* 204 No Content */

Deleting an already-deleted file returns 404, so the call is safe to retry but not idempotent in status code. Anything that isn't one of your generated PDFs — another account's file, a document preview — also returns 404 rather than 403.

Errors

StatuserrorWhen
400VALIDATION_ERRORpath is missing or empty.
401UNAUTHORIZEDMissing or invalid credentials.
404NOT_FOUNDNo such generated file for this account, or it was already deleted.

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.