Getting Started
Dieser Guide zeigt, wie Sie einen Shop, ein ERP oder MIS mit radixSubmit integrieren: einen Auftrag programmatisch erstellen, Ihren Kunden einen Upload-Link übergeben, auf den Completion-Webhook reagieren und die fertigen Dateien herunterladen.
Die interaktive Endpoint-Referenz finden Sie in der API-Referenz. Die zugehörige maschinenlesbare Spezifikation ist shop-openapi.yaml — importieren Sie sie in Postman, Insomnia oder Ihr Codegen-Tool.
Voraussetzungen
Ein Mandanten-Admin stellt Ihnen ein clientId + clientSecret-Paar aus und konfiguriert (optional) den Order-Completion-Webhook, damit Sie auf Bestätigungen reagieren können. Alle Beispiele verwenden https://api.example.com — ersetzen Sie das durch die radixSubmit-API-Basis- URL, die Sie mit Ihren Zugangsdaten erhalten.
End-to-end-Ablauf
1. POST /auth/token → { token: "app_…", expireAt }
2. POST /orders → { order: { id, products[…] } }
3. POST /orders/{id}/share → { sharedKey }
4. PORTAL_URL/s/{key} senden → Kunde lädt hoch, ordnet zu, preflightet, bestätigt
5. Webhook: order.confirmed → POST an Ihren Endpoint (signiert)
6. GET /products/{id}/resources → preflightete Dateien herunterladen (presigned URLs)1. Authentifizieren
Tauschen Sie Ihre Client-Zugangsdaten gegen einen Bearer-Token. Der Token ist 24 Stunden gültig — cachen Sie ihn bis expireAt und authentifizieren Sie sich bei jedem 401 neu.
curl -X POST https://api.example.com/auth/token \
-H "Content-Type: application/json" \
-d '{ "clientId": "YOUR_CLIENT_ID", "clientSecret": "YOUR_CLIENT_SECRET" }'{ "token": "app_eyJhbGciOi...", "type": "Bearer", "expireAt": 1715200000 }Senden Sie ihn bei jedem weiteren Aufruf mit:
Authorization: Bearer app_eyJhbGciOi...Einfacher: statischer API-Key (ohne Token-Tausch)
Lieber in einem Schritt? Authentifizieren Sie sich per HTTP Basic mit Ihrer clientId als Benutzername und clientSecret als Passwort — kein /auth/token, kein Ablauf:
curl -u "YOUR_CLIENT_ID:YOUR_CLIENT_SECRET" \
-X POST https://api.example.com/orders/simple \
-H "Content-Type: application/json" -d @order.jsonBeide Wege liefern dieselbe Application-Identität. Immer über HTTPS senden.
2. Auftrag erstellen
Es gibt zwei Wege, einen Auftrag anzulegen:
POST /orders/simple— ein flacher, template-freier Payload für die häufigen Fälle (Flachprodukte, gefalzte Leaflets, rückendrahtgeheftete Broschüren). Für die meisten Shops empfohlen.POST /orders— das vollständige verschachtelte XJDF-basierte Modell (Klebebindung, Hardcover, Multi-Variant). Weiter unten dokumentiert.
Quick order — POST /orders/simple
Beschreiben Sie jede Position flach; das Backend expandiert sie in die volle Hierarchie. Mit options.createShareLink erhalten Sie die Portal-URL direkt zurück.
curl -X POST https://api.example.com/orders/simple \
-H "Authorization: Bearer app_..." \
-H "Content-Type: application/json" \
-d @order.json{
"order": {
"name": "Webshop-Bestellung #10249",
"externalOrderId": "SHOP-10249",
"customer": { "name": "Acme Retail AG", "email": "max@acme.example" },
"items": [
{ "type": "flat", "name": "A5 Flyer", "quantity": 500, "format": "A5", "pages": 2, "colors": "4/4" },
{
"type": "booklet", "name": "Sommerkatalog", "quantity": 1000,
"format": "A4", "bodyPages": 32, "coverPages": 4, "binding": "saddle",
"cover": { "name": "Umschlag", "colors": "4/4" },
"body": { "name": "Inhalt", "colors": "4/4" }
}
]
},
"options": { "createShareLink": true }
}Die Antwort enthält den neuen Auftrag und — bei Bedarf — eine fertige shareUrl:
{
"id": "9b2f8c1a-…",
"orderNumber": 10249,
"status": "READY",
"milestone": "10_CREATED",
"shareUrl": "https://portal.example.com/portal/3f9a…",
"products": [
{ "id": "2c1d…", "name": "A5 Flyer", "type": "FlatWork" },
{ "id": "7e4b…", "name": "Sommerkatalog", "type": "Booklet", "displayType": "Booklet" }
]
}Konventionen
- Format: benannt (
A6/A5/A4/A3/DL) oder explizitwidth/heightin mm. colors: Farben vorne/hinten —4/4(CMYK beidseitig),4/0(nur vorne),1/0(Schwarz vorne).material:{ weight, type }—weightin g/m².- Broschüren: nur Rückendrahtheftung (Phase 1),
bodyPagesein Vielfaches von 4; Farben procover/body;cover: nullfür eine selbstdeckende Broschüre.
Für Klebebindung, Hardcover oder Multi-Variant nutzen Sie die vollständige Form unten.
Vollständiger Auftrag — POST /orders
Aufträge nutzen ein XJDF-basiertes Auftragsmodell — eine verschachtelte Hierarchie:
order
└── products[] (1..n) was produziert wird
└── parts[] (1..n) Bestandteile (Umschlag, Inhalt, Einleger)
└── pageRanges[] (1..n) Seitengruppen innerhalb eines Teils
└── variants[] (0..n) Versionen pro Auflage (A/B, Sprache)Eine minimale mehrteilige Broschüre (Rückendrahtheftung, Umschlag + Inhalt):
curl -X POST https://api.example.com/orders \
-H "Authorization: Bearer app_..." \
-H "Content-Type: application/json" \
-d @order.json{
"order": {
"name": "Summer catalogue — A4 8 pages 4/4",
"externalOrderId": "SHOP-12345",
"jobCustomer": { "name": "Acme Retail AG" },
"jobContacts": [
{ "contactType": "Buyer", "name": "Max Mustermann", "email": "max@acme.example" }
],
"products": [
{
"name": "Summer catalogue",
"type": "Booklet",
"amount": 500,
"parts": [
{
"name": "Body",
"type": "Body",
"intent": {
"layoutIntent": {
"pages": 4,
"sides": "TwoSidedHeadToHead",
"dimensions": { "width": 595.276, "height": 793.701 },
"numberUp": { "width": 1, "height": 1 }
}
},
"pageRanges": [
{ "value": "1-4", "variants": [{ "name": "1", "amount": 500 }] }
]
},
{
"name": "Cover",
"type": "Cover",
"intent": {
"layoutIntent": {
"pages": 4,
"sides": "TwoSidedHeadToHead",
"dimensions": { "width": 595.276, "height": 793.701 },
"numberUp": { "width": 1, "height": 1 }
}
},
"pageRanges": [
{ "value": "1-4", "variants": [{ "name": "1", "amount": 500 }] }
]
}
]
}
]
}
}Die Antwort enthält den erstellten Auftrag mit IDs, die Sie später brauchen (order.id, jede product.id, jede variant.id), sowie einen standardOrder-XJDF-Export:
{
"order": {
"id": "a7e12b7a-4c3d-4b1a-9e8f-1d2c3b4a5f6e",
"orderNumber": 1042,
"status": "new",
"milestone": "created",
"products": [{ "id": "c3f3…", "parts": [ /* … */ ] }],
"standardOrder": { "...": "XJDF-compliant export" }
}
}Feldanforderungen
layoutIntent.pages, sides, dimensions und numberUp sind erforderlich, damit das Portal Seitenplätze rendern kann. colorIntent und mediaIntent verbessern das Preflight-Routing (sonst werden FOGRA-Defaults verwendet). Maße sind in Punkt (A4 = 595.276 × 841.890). Die API-Referenz enthält das vollständige Schema und ausführlichere Beispiele.
Häufige Fehler
| HTTP | code | Ursache |
|---|---|---|
| 400 | invalid_argument | Pflichtfeld fehlt, unbekanntes Enum oder inkonsistente variants[].amount-Summe vs. product.amount |
| 401 | unauthenticated | Fehlender / abgelaufener / ungültiger Bearer-Token |
| 403 | permission_denied | Token gehört zu einem anderen Mandanten |
3. Upload-Link erstellen
Sobald der Auftrag existiert, erstellen Sie einen Share-Link — die URL, über die Ihre Kunden hochladen. Optional hängen Sie Metadaten an, um die Kunden zu identifizieren.
curl -X POST https://api.example.com/orders/{ORDER_ID}/share \
-H "Authorization: Bearer app_..." \
-H "Content-Type: application/json" \
-d '{ "metadata": { "externalUserId": "shop-user-998", "externalUserEmail": "buyer@example.com" } }'{ "sharedKey": "sk_…" }Bauen Sie die URL, die Sie per E-Mail senden oder auf die Sie weiterleiten:
${PORTAL_URL}/s/${sharedKey}${PORTAL_URL} ist die Portal-Domain Ihres Mandanten (z. B. https://portal.ihre-firma.de). Der Link ist 7 Tage gültig. Ein neuer Share widerruft den vorherigen; Sie können auch explizit mit DELETE /orders/{ORDER_ID}/share widerrufen.
4. „Redirect-URL" — auf den Abschluss reagieren
Wie der Abschluss heute zugestellt wird
Das Portal nimmt keine browserseitige Redirect-URL pro Auftrag entgegen. Stattdessen liefert das Backend bei Bestätigung durch die Kunden einen signierten order.confirmed-Webhook an die in Ihren Mandanteneinstellungen konfigurierte URL. Behandeln Sie diesen Webhook als Ihr „Redirect"-/Rückkehrsignal. (Falls Sie eine echte Browser-Weiterleitung nach der Bestätigung brauchen, ist das eine mögliche künftige Erweiterung — sprechen Sie uns an.)
Webhook konfigurieren
Bitten Sie Ihren Mandanten-Admin, unter Mandanteneinstellungen → Integrationen → Webhook zu setzen:
orderCompletionUrl— Ihr HTTPS-Endpoint.secret— ein gemeinsames Secret (32+ zufällige Bytes empfohlen).
Ist kein Webhook konfiguriert, wird nichts gesendet — pollen Sie als Fallback GET /orders/{id}.
Payload
{
"event": "order.confirmed",
"timestamp": "2026-04-18T14:32:09.117Z",
"order": {
"id": "a7e12b7a-...",
"name": "Summer catalogue 2026",
"reference": "SHOP-12345",
"confirmedAt": "2026-04-18T14:31:55.000Z",
"products": [
{
"id": "c3f…",
"name": "32-page booklet A4",
"resources": [
{
"id": "r01…",
"name": "booklet-preflighted.pdf",
"mimeType": "application/pdf",
"downloadUrl": "https://s3.../preflighted.pdf?X-Amz-Signature=…"
}
]
}
]
}
}Zustellsemantik
- Retries: bis zu 4 Versuche mit Backoff
0s, 1s, 3s, 9s. - Erfolg: beliebiger HTTP-
2xx. - Kein Dedup-Header — machen Sie Ihren Handler idempotent auf
order.id.
Signatur verifizieren
Jede Anfrage trägt X-Webhook-Signature: <hex>, wobei <hex> = HMAC-SHA256(rawBody, secret). Verifizieren Sie gegen den Raw-Body vor dem JSON-Parsen — erneutes Serialisieren ändert den Digest.
import crypto from 'node:crypto'
import express from 'express'
const app = express()
app.post(
'/webhooks/radix-submit',
express.raw({ type: 'application/json' }),
(req, res) => {
const expected = crypto
.createHmac('sha256', process.env.RADIX_SUBMIT_WEBHOOK_SECRET)
.update(req.body) // raw Buffer
.digest('hex')
const got = req.header('X-Webhook-Signature')
if (!got || !crypto.timingSafeEqual(Buffer.from(expected), Buffer.from(got))) {
return res.status(401).send('bad signature')
}
const payload = JSON.parse(req.body.toString('utf8'))
// …payload.order verarbeiten (idempotent auf payload.order.id)…
res.status(200).end()
},
)5. Preflightete Dateien herunterladen
Die downloadUrls im Webhook sind 7 Tage gültige presigned S3-URLs — laden Sie möglichst sofort herunter. Um die Ressourcen eines Produkts später abzurufen (oder nach Ablauf eines Links), listen Sie sie per Produkt-ID (order.products[].id). Download-URLs sind standardmäßig enthalten:
curl "https://api.example.com/products/{PRODUCT_ID}/resources" \
-H "Authorization: Bearer app_..."{
"resources": [
{
"id": "r01…",
"name": "booklet-preflighted.pdf",
"mimeType": "application/pdf",
"pageCount": 32,
"type": "Preflighted",
"downloadUrl": "https://s3.../preflighted.pdf?X-Amz-Signature=…"
}
]
}Das liefert die Ressourcen auf Produktebene — das zusammengeführte, preflightete Ergebnis, das Sie an die Produktion weitergeben. Mit ?type=Preflighted filtern oder mit ?withDownloadUrls=false die presigned URLs weglassen. (Die kundenseitigen Eingaben pro Variante liegen unter order.products[].parts[].pageRanges[].variants[].resources[] in der Auftragsantwort.)
Endpoint-Referenz
| Methode | Pfad | Zweck |
|---|---|---|
| POST | /auth/token | Zugangsdaten tauschen → 24h-Bearer-Token |
| POST | /orders | Auftrag erstellen (XJDF-basierter Auftrag) |
| GET | /orders/{id} | Auftrag + Hierarchie + Status abrufen |
| POST | /orders/{id}/share | 7-Tage-Share-Link erstellen |
| DELETE | /orders/{id}/share | Aktive Shares widerrufen |
| GET | /products/{id}/resources | Ressourcen eines Produkts + presigned Downloads auflisten |
| Outbound | POST {orderCompletionUrl} | order.confirmed-Webhook bei Bestätigung |
Vollständige Request-/Response-Schemata und „Try it out" finden Sie in der API-Referenz.