CH Layer docs
API docs for Swiss infrastructure workflows.
Create an account, generate a test API key, send your first QR-bill request, then activate billing before live production traffic.
Base URL
https://api.ch-layer.ch
Authentication
Send API keys as bearer tokens: Authorization: Bearer sk_test_... or Authorization: Bearer sk_live_.... API keys are scoped to one dashboard account.
Test and live mode
Test keys include X-Test-Mode: true and are meant for onboarding/sandbox use. Live keys are for production traffic and require active billing for paid plans.
Plan enforcement
Live API traffic for Starter, Growth, and Scale requires billing status active or trialing. Inactive, past-due, or canceled billing returns payment_required. Test keys remain available so developers can fix integration issues.
Rate limits
Responses include X-RateLimit-Limit, X-RateLimit-Remaining, and X-RateLimit-Reset. Free: 10/min; Starter: 60/min; Growth: 200/min; Scale: 1000/min. Monthly limits are enforced per account and environment.
Idempotency
For create/render requests, send Idempotency-Key with a stable unique value such as your invoice ID. Replays return the original response instead of duplicating work.
QR-bill validation schema
The first production module validates and renders Swiss QR-bill payloads. Address fields use building_number and town.
{
"iban": "CH9300762011623852957",
"amount": 199.95,
"currency": "CHF",
"reference_type": "NON | SCOR | QRR",
"reference": null,
"creditor": {
"name": "Acme AG",
"street": "Musterstrasse",
"building_number": "1",
"postal_code": "8000",
"town": "Zürich",
"country": "CH"
},
"debtor": {
"name": "Customer AG",
"street": "Bahnhofstrasse",
"building_number": "10",
"postal_code": "3000",
"town": "Bern",
"country": "CH"
},
"unstructured_message": "Optional payment message"
}Valid QRR example
Use QRR only with a QR-IBAN and a valid 27-digit QR reference.
{
"iban": "CH4431999123000889012",
"amount": 249.0,
"currency": "CHF",
"reference_type": "QRR",
"reference": "210000000003139471430009017",
"creditor": {
"name": "Acme AG",
"street": "Musterstrasse",
"building_number": "1",
"postal_code": "8000",
"town": "Zürich",
"country": "CH"
},
"unstructured_message": "Invoice 2026-0001"
}Endpoints
POST /v1/qr-bills
POST /v1/qr-bills/validate
POST /v1/qr-bills/render
POST /v1/qr-bills/parse
POST /v1/qr-bills/batchRender PDF/SVG/PNG
Render calls use one endpoint and select svg, png, or pdf in the JSON body.
curl https://api.ch-layer.ch/v1/qr-bills/render \
-H "Authorization: Bearer sk_test_..." \
-H "Content-Type: application/json" \
-H "Idempotency-Key: invoice-2026-0001-pdf" \
-d '{
"format": "pdf",
"qr_bill": {
"iban": "CH9300762011623852957",
"amount": 199.95,
"currency": "CHF",
"reference_type": "NON",
"reference": null,
"creditor": {
"name": "Acme AG",
"street": "Musterstrasse",
"building_number": "1",
"postal_code": "8000",
"town": "Zürich",
"country": "CH"
}
},
"options": { "language": "en" }
}' \
--output qr-bill.pdfRender response formats
// Render output is selected by JSON body, not by path suffix:
{ "format": "svg", "qr_bill": { ... } }
{ "format": "png", "qr_bill": { ... } }
{ "format": "pdf", "qr_bill": { ... } }
// Successful render responses are downloadable files:
Content-Type: image/svg+xml | image/png | application/pdf
Content-Disposition: attachment; filename=qr-bill.svg|png|pdfcurl · SVG
curl https://api.ch-layer.ch/v1/qr-bills/render \
-H "Authorization: Bearer sk_test_..." \
-H "Content-Type: application/json" \
-H "Idempotency-Key: invoice-2026-0001-svg" \
-d '{
"format": "svg",
"qr_bill": {
"iban": "CH9300762011623852957",
"creditor": {
"name": "Acme AG",
"street": "Musterstrasse",
"building_number": "1",
"postal_code": "8000",
"town": "Zürich",
"country": "CH"
},
"amount": 199.95,
"currency": "CHF",
"reference_type": "NON",
"reference": null,
"unstructured_message": "Invoice 2026-0001"
}
}' \
--output qr-bill.svgJavaScript · PNG
const response = await fetch("https://api.ch-layer.ch/v1/qr-bills/render", {
method: "POST",
headers: {
"Authorization": "Bearer sk_test_...",
"Content-Type": "application/json",
"Idempotency-Key": "invoice-2026-0001-png"
},
body: JSON.stringify({
format: "png",
qr_bill: {
"iban": "CH9300762011623852957",
"creditor": {
"name": "Acme AG",
"street": "Musterstrasse",
"building_number": "1",
"postal_code": "8000",
"town": "Zürich",
"country": "CH"
},
"amount": 199.95,
"currency": "CHF",
"reference_type": "NON",
"reference": null,
"unstructured_message": "Invoice 2026-0001"
}
})
});
if (!response.ok) throw new Error(await response.text());
const file = await response.blob();Python · PDF
import json
import requests
response = requests.post(
"https://api.ch-layer.ch/v1/qr-bills/render",
headers={
"Authorization": "Bearer sk_test_...",
"Content-Type": "application/json",
"Idempotency-Key": "invoice-2026-0001-pdf",
},
json={
"format": "pdf",
"qr_bill": json.loads("{\n \"iban\": \"CH9300762011623852957\",\n \"creditor\": {\n \"name\": \"Acme AG\",\n \"street\": \"Musterstrasse\",\n \"building_number\": \"1\",\n \"postal_code\": \"8000\",\n \"town\": \"Zürich\",\n \"country\": \"CH\"\n },\n \"amount\": 199.95,\n \"currency\": \"CHF\",\n \"reference_type\": \"NON\",\n \"reference\": null,\n \"unstructured_message\": \"Invoice 2026-0001\"\n}"),
},
)
response.raise_for_status()
with open("qr-bill.pdf", "wb") as output:
output.write(response.content)Error responses
Errors use a stable envelope with error.type and error.message. Common types: authentication_error, validation_error, rate_limit_exceeded, payment_required, and configuration_error.
{
"error": {
"type": "payment_required",
"message": "Live API access requires an active subscription."
}
}