Skip to content

Data contract

A template is not just a layout: it declares the variables it reads — names, types, whether they repeat per record, and example values. That declaration is the data contract between the template’s designer and the system that calls the API.

Terminal window
curl https://composepdf.com/v1/canvases/{canvasId}/schema \
-H "x-api-key: $COMPOSEPDF_API_KEY"

The response is a JSON Schema describing the payload for POST /v1/canvases/{canvasId}/pdf — generate types from it, validate in CI, or build a form against it.

The same contract is checked server-side before layout. A payload that does not satisfy it never reaches rendering; the API answers 400 with the exact violating paths:

{
"error": "data does not satisfy the template's declarations",
"violations": ["customer.name", "items[2].price"]
}

With dataList, paths name the record: dataList[3].customer.name.

Why contracts instead of “render whatever arrives”

Section titled “Why contracts instead of “render whatever arrives””

A wrong payload that renders something is the worst failure mode: the mistake surfaces as a blank field on a customer-facing document, weeks later. The contract moves that failure to the API boundary, where your integration tests can see it — a 400 today instead of an empty invoice in production.

Enum-typed variables are part of the contract too: a value outside the declared set is a violation, and the schema publishes the allowed members so your form can render them as options.