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.
- The key is a static shared secret. Anyone who can read it can render arbitrary documents against your server, and there is no per-user identity, session or key revocation during a request.
- Rendering is CPU-bound. An untrusted caller can consume the render and rate-limit budget of every other caller.
- The service has no user accounts, TLS termination, or browser-grade CORS by
default.
JSONPRESS_CORS_ORIGINSexists for trusted internal origins only;*is rejected at startup.
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
- A render can legitimately take up to
JSONPRESS_TIMEOUT_SECS(30 s by default); set the client timeout higher. 429 rate_limitedcarriesRetry-After: 60; the limiter is a fixed one-minute window per key.503 overloadedmeans no render permit was free. Retry with backoff; do not queue unboundedly in the caller.504 render_timeoutis not retryable without reducing the rows or template complexity.