JSON PressBack to site

Integrating

JSON Press is a server-to-server service. A backend holds the API key, sends the template and rows, and stores or streams the returned document.

curl

The checked-in fixture is a complete template:

jq -n --slurpfile template crates/jsonpress-core/testdata/invoice.json \
  '{template: $template[0],
    data: {rows: [{CUSTOMER: "Acme Ltd", AMOUNT: "1250.00"},
                  {CUSTOMER: "Beta Trading", AMOUNT: "89.50"}]},
    params: {month: "11"},
    output: "pdf"}' |
curl -sS http://localhost:5000/v1/renders \
  -H 'Authorization: Bearer dev' \
  -H 'Content-Type: application/json' \
  --data-binary @- \
  -o invoice.pdf

Set output to "docx" for an editable Word document, "xlsx" for a spreadsheet, or "csv" for a flat table. The server sends a Content-Disposition header with the corresponding slugged filename.

A failure still returns JSON, so check the status before treating the body as a document:

curl -sS -o response.bin -w '%{http_code}\n' ...

Node (18+, built-in fetch)

import { readFile, writeFile } from "node:fs/promises";

const apiKey = process.env.JSONPRESS_API_KEY;
if (!apiKey) throw new Error("JSONPRESS_API_KEY is not set");

const template = JSON.parse(await readFile("invoice.json", "utf8"));
const response = await fetch("http://localhost:5000/v1/renders", {
  method: "POST",
  headers: {
    Authorization: `Bearer ${apiKey}`,
    "Content-Type": "application/json",
  },
  body: JSON.stringify({
    template,
    data: {
      rows: [
        { CUSTOMER: "Acme Ltd", AMOUNT: "1250.00" },
        { CUSTOMER: "Beta Trading", AMOUNT: "89.50" },
      ],
    },
    params: { month: "11" },
    output: "pdf",
  }),
});

if (!response.ok) {
  // Every error is {"error":{"code","message","details"}}.
  const { error } = await response.json();
  throw new Error(`${response.status} ${error.code}: ${error.message}`);
}

const bytes = Buffer.from(await response.arrayBuffer());
const disposition = response.headers.get("content-disposition") ?? "";
const fileName = /filename="([^"]+)"/.exec(disposition)?.[1] ?? "report.pdf";
await writeFile(fileName, bytes);

Node 18+ is enough; no SDK is needed. Any HTTP client that can send a JSON body and receive bytes will work.

Python

import json
import os
import re
from pathlib import Path

import requests

api_key = os.environ["JSONPRESS_API_KEY"]
template = Path("invoice.json").read_text(encoding="utf-8")

response = requests.post(
    "http://localhost:5000/v1/renders",
    headers={
        "Authorization": f"Bearer {api_key}",
        "Content-Type": "application/json",
    },
    json={
        "template": json.loads(template),
        "data": {
            "rows": [
                {"CUSTOMER": "Acme Ltd", "AMOUNT": "1250.00"},
                {"CUSTOMER": "Beta Trading", "AMOUNT": "89.50"},
            ]
        },
        "params": {"month": "11"},
        "output": "pdf",
    },
    # Keep the client timeout above JSONPRESS_TIMEOUT_SECS (30 s by default).
    timeout=60,
)

if response.status_code != 200:
    error = response.json()["error"]
    raise RuntimeError(f"{response.status_code} {error['code']}: {error['message']}")

disposition = response.headers.get("content-disposition", "")
match = re.search(r'filename="([^"]+)"', disposition)
Path(match.group(1) if match else "report.pdf").write_bytes(response.content)

requests is only used to keep the example short; the standard library urllib.request works the same way.

Server-to-server only

Do not call this service directly from end-user browsers and do not ship the API key in front-end code.

Put your own authenticated backend or proxy in front of JSON Press, keep the key on the server, and enforce your own authorization before forwarding a render. If a browser must download a document, stream the bytes from your backend rather than exposing the renderer.

Handling parameters

Request params is a string map, even when the template declares an integer/boolean parameter in its designer metadata. Convert values in the caller (to_number(month), "true") and keep formatting in the template's expressions, for example format_number(month, "@L 99").

Timeouts and retries