Partner-API
HTTP /api/v1 — Menü, Shop-Konfiguration, Kasse, Zeitfenster, Reservierungen, Anfragen, Catering, ETA-Angebot und Bestellungen. Eine API-Beschreibung, abgestimmt mit OpenAPI 3.1.
Die Partner-API v1 ist die HTTP-Schnittstelle unter https://delieta.pl/api/v1. Der API-Schlüssel, den Delieta einer Firma ausgestellt hat, bezeichnet diese Firma: Keine Anfrage hat ein Firmenfeld. Das ist die einzige Beschreibung für Menschen; die Beschreibung für Maschinen ist GET /api/v1/openapi (OpenAPI 3.1, ohne Schlüssel).
Veröffentlichte Operationen: GET /menu, GET /shop-config, POST /shop-heartbeat, GET /checkout/slots, POST /checkout, POST /checkout/quote, GET /checkout/result, GET /events, POST /quotes, POST /orders, GET /orders/{id}, GET /orders/{id}/status, GET /floor-plan, GET /reservations/availability, POST /reservations, POST /inquiries, POST /catering-orders, GET /catering-orders und GET /catalog-images/{id}/{filename}.
flowchart LR
bearer["Klucz API"] --> verify["Klucz jest firma"]
verify --> scope{"scope"}
scope -->|"catalog:read"| menu["GET /menu"]
scope -->|"checkout:create"| checkout["POST /checkout"]
scope -->|"orders:write"| handoff["POST /orders"]
handoff --> same["to samo zamowienie co panel"]Schlüssel und Scopes
Zwei gleichwertige Header. Der zuerst gefundene gilt; ein Schlüssel im Query-String wird nie akzeptiert.
Authorization: Bearer dlt_live_…
x-delieta-api-key: dlt_live_…
Reihenfolge der Prüfungen: Schlüssel → Scope → Testschlüssel (nur bei Schreibzugriff) → Limit. Fehlender oder falscher Schlüssel → 401 unauthorized.
| Scope | Schlüsselart | Operationen |
|---|---|---|
catalog:read | veröffentlichbar | GET /api/v1/menu, GET /api/v1/shop-config, GET /api/v1/checkout/slots, GET /api/v1/events, GET /api/v1/orders/{id}/status, GET /api/v1/floor-plan, GET /api/v1/reservations/availability und POST /api/v1/shop-heartbeat. menu:read ist ein Alias für ältere Schlüssel. |
checkout:create | veröffentlichbar | POST /api/v1/checkout, POST /api/v1/checkout/quote, GET /api/v1/checkout/result, POST /api/v1/inquiries und POST /api/v1/reservations (eine web-Buchung). |
eta:quote | geheim | POST /api/v1/quotes |
orders:write | geheim | POST /api/v1/orders und GET /api/v1/orders/{id} |
reservations:write | geheim | POST /api/v1/reservations aus dem eigenen System der Firma (eine api-Buchung) |
catering:write | geheim | POST /api/v1/catering-orders und GET /api/v1/catering-orders |
Ein veröffentlichbarer Schlüssel ist genau catalog:read + checkout:create und nichts weiter: Ihn hält der Shop, und der Shop bewahrt ihn serverseitig auf, nie im Browser. Ein veröffentlichbarer Schlüssel kann nicht orders:write, eta:quote, reservations:write oder catering:write bekommen. Eine leere Scope-Liste öffnet nichts — 403 scope_required. Eine Reservierung zählt den Scope nach Schlüsselart: veröffentlichbar braucht checkout:create, geheim reservations:write.
dlt_test_… liest die echten Produkte der Firma und berechnet Angebote. Jeder Schreibzugriff wird mit 403 test_key_readonly abgelehnt: POST /api/v1/checkout, /orders, /reservations, /inquiries, /catering-orders und /shop-heartbeat. Es gibt keinen separaten Testdatensatz.
Limits
Limits gelten pro Schlüssel in einem 60-s-Fenster. Standard: 60 Schreibzugriffe und 600 Lesezugriffe pro Minute, getrennt gezählt, damit häufiges Abfragen nicht das Schreiblimit verbraucht. Schreibzugriffe sind Bestellung anlegen, Kasse, Reservierung, Anfrage, Catering-Auftrag und Angebot. Lesezugriffe sind alles andere. Bei Überschreitung: 429 rate_limited; beachten Sie Retry-After und RateLimit-Limit / RateLimit-Remaining / RateLimit-Reset.
Einen veröffentlichbaren Schlüssel nutzen viele Besucher, daher zählt sein Limit pro Besucher. Senden Sie die Adresse des Gastes im Header x-delieta-visitor; ohne ihn zählt der Origin. Zusätzlich gilt eine gemeinsame Obergrenze vom 20-Fachen des Limits. Ein geheimer Schlüssel ignoriert diesen Header.
Fehler
Jede Ablehnung der v1 ist application/problem+json (RFC 9457). Erkennen Sie den Fehler an code, nicht am Text.
{
"type": "https://delieta.pl/problems/unauthorized",
"title": "Unauthorized",
"status": 401,
"code": "unauthorized",
"detail": "Provide a valid API key as `Authorization: Bearer dlt_live_…`."
}
Ausnahme: GET /api/v1/catalog-images/{id}/{filename} antwortet bei Überschreitung des Limits mit einem einfachen { "error": "rate_limited" } und 429, nicht mit problem+json.
| code | status | wann |
|---|---|---|
unauthorized | 401 | fehlender oder falscher Schlüssel |
scope_required | 403 | Schlüssel ohne den nötigen Scope |
test_key_readonly | 403 | dlt_test_… versuchte einen Schreibzugriff |
module_disabled | 403 | das Modul der Firma ist aus (Tarif oder Schalter des Inhabers): Bestellungen bei POST /checkout und GET /checkout/slots, Reservierungen, Catering. Der Status einer aufgegebenen Bestellung funktioniert weiter |
invalid_request | 400 | Body ist kein JSON oder scheitert an der Validierung; Details in errors[] (z. B. unknown_field bei POST /orders) |
idempotency_key_required | 400 | Schreibzugriff ohne Idempotency-Key (8–255 Zeichen) |
line_price_missing / line_name_missing / total_out_of_range | 400 | eine Bestellzeile oder die Summe lässt sich nicht bepreisen |
payload_too_large | 413 | Body einer Anfrage oder eines Catering-Auftrags über 64 KiB |
not_found | 404 | keine solche Bestellung für diesen Schlüssel (eine fremde ID antwortet gleich) |
tenant_not_found | 404 | die Firma des Schlüssels existiert nicht mehr |
idempotency_key_conflict | 409 | gleicher Idempotenzschlüssel, anderer Body |
conflict | 409 | z. B. die Anfrage ist schon mit einem anderen Auftrag verknüpft |
venue_not_configured | 409 | Angebot, aber die Firma hat keine Standortkoordinaten |
out_of_range | 409 | Preisangebot oder Checkout mit Lieferung: die Adresse liegt außerhalb des Liefergebiets der Firma (errors[] am Feld address). Bei einer Reservierung ist derselbe code ein 422 (Datum außerhalb des Bereichs) |
delivery_unconfigured | 409 | Checkout mit Lieferung: das Lokal hat Kilometerstufen, aber keine Adresse oder keine eingestellte Lieferung. Abholung funktioniert |
total_changed | 409 | Checkout: das angegebene expectedTotalGrosze stimmt nicht mit dem jetzt berechneten Betrag überein. Der Server hat es nicht zur Preisberechnung genutzt |
product_outside_hours | 409 | Checkout: ein Artikel liegt zum gewählten Zeitpunkt außerhalb seiner Verfügbarkeitszeiten; Zeilen in errors[] an items.N.itemId |
shop_closed | 409 | der Shop ist jetzt geschlossen und nimmt keine Bestellung „so schnell wie möglich“ an |
shop_unconfigured | 409 | kein gespeicherter Shop-Origin für die Rücksprungadressen der Zahlung |
connect_not_ready | 409 | das Konto für Online-Zahlungen kann noch keine Zahlungen annehmen |
sample_mode | 409 | Online-Zahlung in einem Shop mit Beispieldaten, während Zahlungen live sind |
slot_unavailable | 409 | requestedFor ist keiner der Zeitpunkte aus GET /checkout/slots; bei einer Reservierung: der Termin ist vergeben |
table_taken | 409 | Reservierung: der Tisch ist vergeben (auch wenn jemand anders ihn kurz zuvor gebucht hat) |
fulfilment_unavailable | 409 | der Shop bietet diese Übergabe nicht an (Lieferung oder Abholung) |
payment_unavailable | 409 | der Shop akzeptiert diese Zahlung nicht (online oder bar) |
duplicate_external_ref | 409 | dieses externalRef hat für die Firma schon eine Bestellung erzeugt |
unprocessable | 422 | unbekannter oder nicht verfügbarer Artikel (errors[].code: product_unknown, product_unavailable); unbekanntes Catering-Format |
below_minimum | 422 | Warenkorb unter dem Mindestbestellwert des Shops (nur Gerichte, ohne Verpackung und Lieferung; Feld items) |
product_menu_only | 422 | Checkout: ein Artikel ist „Tylko w karcie“ (nur auf der Karte) und online nicht bestellbar; Zeilen in errors[] an items.N.itemId. Treffen beide Fälle zu, gewinnt dieser |
address_not_found | 422 | Checkout mit Lieferung: die Adresse ließ sich nicht ermitteln (Feld address) |
headcount_out_of_range | 422 | Catering-Anfrage: die Personenzahl liegt außerhalb des Bereichs des Formats (catering.headcount) |
venue_not_offered | 422 | Catering-Anfrage: das Format bedient den gewählten Ort nicht (catering.venue) |
unknown_add_on | 422 | Catering-Anfrage: der Zusatz gehört nicht zum gewählten Format (catering.addOns.N.key) |
unknown_event, event_not_bookable, event_past, event_phone_only | 422 | Veranstaltungsanfrage: keine solche Veranstaltung, sie nimmt keine Anfragen an, sie hat schon stattgefunden oder die Anmeldung läuft nur telefonisch (event.eventId) |
unknown_occurrence, event_in_past | 422 | Veranstaltungsanfrage: der Termin steht nicht in der Liste oder liegt in der Vergangenheit (event.occurrenceId, event.preferredDate) |
participants_out_of_range | 422 | Veranstaltungsanfrage: die Teilnehmerzahl liegt außerhalb des Bereichs der Veranstaltung (event.participants) |
option_invalid | 422 | unbekannte Option, fehlende Pflichtgruppe oder zu viele Auswahlen in einer Gruppe |
validation_failed / party_too_large | 422 | Reservierung: schlechter Inhalt, Gruppe größer als der Raum fasst |
rate_limited | 429 | Minutenlimit überschritten; siehe Limits |
internal | 500 | ein Fehler auf unserer Seite; erneut versuchen |
create_failed | 500 | POST /orders: die Bestellung ließ sich nicht speichern; mit demselben Idempotency-Key wiederholen |
idempotency_unavailable | 503 | der Idempotenz-Speicher ist nicht verfügbar; mit demselben Schlüssel wiederholen |
delivery_quote_unavailable | 503 | Checkout oder Preisangebot mit Lieferung: die Entfernung ließ sich gerade nicht berechnen; erneut versuchen oder Abholung wählen |
module_unavailable | 503 | die Module der Firma ließen sich nicht lesen (das Modul ist nicht aus); gleich erneut versuchen (Retry-After) |
Geld
Ausschließlich ganze Grosze. 3000 sind 30,00 PLN. Keine Floats, kein Währungsfeld an Beträgen. currency in den Antworten ist die Konstante "PLN".
GET /api/v1/menu
Die Produkte der Firma, zu der der Schlüssel gehört. Scope catalog:read. Nicht verfügbare Artikel sind weggelassen — im DTO gibt es kein available-Flag, das der Client behandeln müsste. Artikel „Tylko w karcie“ (nur auf der Karte, orderable: false) stehen in menuOnlyItems, nicht in items; die Verfügbarkeitszeiten stehen in availability. Die Antwort hat kein Flag „jetzt verfügbar“: Der Shop berechnet es selbst aus availability. Die Struktur ändert sich nicht, neue Felder können nur hinzukommen.
GET /api/v1/menu
Authorization: Bearer dlt_live_…
{
"currency": "PLN",
"generatedAt": "2026-08-26T12:00:00.000Z",
"locale": "pl",
"categories": [
{ "id": "c1", "slug": "pizza", "name": "Pizza", "sort": 0, "orderable": true, "availability": null }
],
"items": [
{
"id": "i1",
"slug": "margherita",
"categoryId": "c1",
"name": "Margherita",
"description": null,
"basePriceGrosze": 3200,
"allergens": [],
"modifierGroups": [],
"imageUrl": null,
"featured": false,
"sort": 0,
"orderable": true,
"availability": null
}
],
"menuOnlyCategories": [],
"menuOnlyItems": []
}
Ein leeres Array von Kategorien und Artikeln ist eine gültige Antwort, kein Fehler. Cache-Control: no-store. Die Antwort enthält den Header x-delieta-cache-tag; ein Shop mit eigenem Cache nutzt ihn als Schlüssel.
Das optionale ?locale= (pl, en, de, uk) liefert Namen und Beschreibungen in dieser Sprache, feldweise, und auf Polnisch, wenn eine Übersetzung fehlt. Für eine Sprache, die die Firma nicht aktiviert hat (oder eine unbekannte), bekommen Sie den Inhalt auf Polnisch; locale in der Antwort sagt, welche Sprache tatsächlich geliefert wurde. Dasselbe ?locale= lesen GET /api/v1/shop-config (Namen der Catering-Formate; liefert auch die aktivierten locales, Polnisch zuerst) und GET /api/v1/floor-plan (Zonennamen).
imageUrl ist die absolute Adresse des Gerichtfotos (die 800-px-Version) oder null, wenn das Gericht keines hat. featured: true heißt, der Inhaber hat das Gericht als „Empfohlen“ markiert.
orderable: false heißt „Tylko w karcie“ (nur auf der Karte): den Artikel mit Preis zeigen, ohne die Schaltfläche „Do koszyka“. Solche Artikel stehen nicht in items, sondern in menuOnlyItems, und eine Kategorie, in der jeder Artikel nur auf der Karte steht, steht in menuOnlyCategories, nicht in categories — ein Client, der diese Schlüssel nicht kennt, zeigt sie einfach nicht und bietet nie etwas an, das sich nicht bestellen lässt. @delieta/shop-runtime führt sie ab 1.8.0 wieder zusammen (withMenuOnly). Der Wert am Artikel berücksichtigt bereits seine Kategorie (Kategorie nur auf der Karte = jeder ihrer Artikel). availability sind die Verfügbarkeitszeiten: { "windows": [{ "days": [1,2,3,4], "from": "12:00", "to": "16:30" }], "fromDate": null, "toDate": null } — bis zu 7 Zeitfenster, Tage ab 0 = Montag, beide Grenzen inklusive, Zeitzone Europe/Warsaw. Der Artikel hat seine eigene Regel, die Kategorie ihre; beide gelten gleichzeitig. Ein Artikel nach seiner Saison (toDate vorbei) kommt gar nicht ins Menü. Das Menü wird gecacht, sagt also nicht, ob ein Artikel jetzt verfügbar ist — berechne das aus der Regel (@delieta/shop-runtime tut das ab 1.8.0: itemSaleState, liveMenu). Der Checkout prüft die Regel für den Erfüllungszeitpunkt: requestedFor oder jetzt + Zubereitungszeit bei „jak najszybciej“.
GET /api/v1/catalog-images//
Ein Foto eines Gerichts oder Catering-Formats. Ohne Schlüssel: Den Browser des Shop-Kunden öffnet die Adresse aus imageUrl, also darf kein Geheimnis verlangt werden. Sie müssen die id und den genauen Dateinamen kennen — ein nicht passendes Paar ist ein 404. ?size=card liefert die 800 px breite Version (ein schmaleres Foto wird unverändert geliefert); ohne den Parameter erhalten Sie das Original.
GET /api/v1/catalog-images/{id}/{filename}?size=card
Die Antwort ist ein 302 zum Dateispeicher oder die Bild-Bytes (image/jpeg, image/png, image/webp oder image/avif). Eine Foto-Adresse ändert nie ihren Inhalt — ein ersetztes Foto bekommt eine neue Adresse —, daher enthält die Antwort Cache-Control: public, max-age=31536000, immutable. Limit: 600 Anfragen pro Minute von einer IP (rate_limited).
GET /api/v1/shop-config
Marke und Regeln des Shops der Firma, zu der der Schlüssel gehört, so wie ihr Shop sie zeigt. Scope catalog:read — derselbe wie beim Menü, ein veröffentlichbarer Schlüssel hat ihn also schon. Die eigenen Markeneinstellungen des Shops haben Vorrang, Feld für Feld; wo sie fehlen, gelten Name, Logo und Farbe der Firma. Die Antwort enthält x-delieta-cache-tag.
GET /api/v1/shop-config
Authorization: Bearer dlt_live_…
{
"name": "Bella Napoli",
"tagline": "Pizza z pieca opalanego drewnem",
"brandColor": "#e2574c",
"logo": "https://delieta.pl/api/v1/catalog-images/{id}/{filename}",
"banner": "https://delieta.pl/api/v1/catalog-images/{id}/{filename}",
"icon": "https://delieta.pl/api/v1/catalog-images/{id}/{filename}",
"currency": "PLN",
"locale": "pl",
"locales": ["pl", "en"],
"layout": "grid",
"links": {
"phone": "+48000000000",
"email": "kontakt@bella.example",
"instagram": "https://instagram.com/bella.example",
"facebook": "https://facebook.com/bella.example",
"address": "Kwiatowa 5, Bydgoszcz",
"mapUrl": "https://www.google.com/maps/dir/?api=1&destination=Kwiatowa%205%2C%20Bydgoszcz"
},
"modules": ["ordering", "reservations", "catering"],
"ordering": {
"fulfilmentModes": ["delivery", "pickup"],
"paymentModes": ["online", "cash"],
"onlineMethods": ["card", "blik"],
"deliveryFeeGrosze": 500,
"minimumOrderGrosze": 4000,
"prepMinutes": 30
},
"hours": {
"days": [
{ "weekday": 0, "closed": false, "open": "10:00", "close": "22:00" },
{ "weekday": 6, "closed": true, "open": "10:00", "close": "22:00" }
]
},
"closures": [
{ "from": "2026-12-24", "to": "2026-12-26", "note": "Święta" }
],
"cateringFormats": [
{
"id": "f1",
"name": "Lunch firmowy",
"description": "Pizza i napoje dla zespołu.",
"fromPriceGrosze": 3500,
"minHeadcount": 10,
"imageUrl": null,
"items": [
{ "productId": "i1", "name": "Margherita", "quantity": 1, "perPerson": true, "note": null }
]
}
],
"generatedAt": "2026-08-26T12:00:00.000Z"
}
| Feld | Bedeutung |
|---|---|
banner | das optionale 3:1-Banner der Firma oder null |
icon | das quadratische Icon der Firma (mindestens 512×512, PNG) für Favicon und App-Icon oder null: dann behält der Shop sein eigenes. Runtime 1.9: shopIcons(config) |
locales | von der Firma aktivierte Sprachen, Polnisch zuerst |
layout | grid oder list |
links | nur, was der Standort ausgefüllt hat; mapUrl wird aus der Adresse gebildet (eine Google-Maps-Route), wenn der Standort keine eigene angegeben hat |
modules | die Shop-Module, die die Firma an hat: ordering, reservations, catering, events. Was hier fehlt, zeigt der Shop nicht |
ordering | wie der Shop Bestellungen annimmt: fulfilmentModes, paymentModes (online nur, wenn der Shop eine Karte belasten kann; die Liste kann leer sein), onlineMethods (card, blik, p24: die auf dem Stripe-Konto aktiven Methoden; leer ohne online; nur diese nennen. Kann leer sein, obwohl online angeboten wird (Delieta hat noch nicht erfasst, welche Methoden aktiv sind): dann ein allgemeines «Płatność online» zeigen und keine Methode nennen), deliveryFeeGrosze, minimumOrderGrosze, prepMinutes. null, wenn das Bestellmodul aus ist: dann kein Kassenformular zeigen |
hours | wöchentliche Öffnungszeiten oder null, wenn der Standort sie nicht gesetzt hat — nehmen Sie dann keine Zeiten an |
closures | kommende geschlossene Tage (from, to, note oder null), Daten in Europe/Warsaw. Ein fehlendes Feld heißt: keine geschlossenen Tage |
cateringFormats | Catering-Formate mit Produktliste (items) und Foto (imageUrl); nur bei eingeschaltetem Catering-Modul. Hier stehen nur Formate mit venue delivery oder both und priceBasis from — die, die ein Shop auf Paket 1.7 korrekt darstellt |
cateringPackages | alle aktiven Formate mit allen Paketfeldern; nur bei eingeschaltetem Catering-Modul. Felder: slug, venue (onsite, delivery, both), priceBasis (from, per_person, from_per_person), minHeadcount/maxHeadcount, sections (Zeilen mit Produkt oder eigenem Namen), included („W cenie”), terms („Warto wiedzieć”) und addOns (key, name, amount, unit: szt, kg, g, l, ml oder porcja, priceGrosze, product?). Shop-Paket 1.8 liest dieses Feld statt cateringFormats |
hours.days hat genau sieben Einträge, einen pro Tag (weekday 0 = Montag … 6 = Sonntag); oben auf zwei gekürzt. open und close sind HH:MM in der Ortszeit des Standorts und zählen nur bei closed: false. Module, ordering und links werden bei jeder Anfrage gelesen. Eine Firma, zu der der Schlüssel gehörte und die nicht mehr existiert → 404 tenant_not_found.
POST /api/v1/shop-heartbeat
Ein ausgerollter Shop meldet, mit welcher Runtime er gebaut wurde. Einmal pro Build, nie pro Anfrage. Scope catalog:read, kein eigener Scope. Der Schreibzugriff betrifft nur die Flottenspalten in der Shop-Zeile dieser Firma. Immer 204, auch wenn die Firma keine Shop-Zeile hat oder der Body unlesbar ist. Ein dlt_test_…-Schlüssel wird mit 403 test_key_readonly abgelehnt. delieta-shop-assert sendet das für Sie; Sie müssen es nicht selbst aufrufen.
POST /api/v1/shop-heartbeat
Authorization: Bearer dlt_live_…
Content-Type: application/json
{ "runtime": "1.6.0", "contract": "v3", "commit": null, "modified": false }
POST /api/v1/checkout
Der Warenkorb des Shops. Scope checkout:create (veröffentlichbarer Schlüssel). Braucht das Bestellmodul. Idempotency-Key ist Pflicht (8–255 Zeichen). Der Client sendet was, nie wie viel: { items: [{ itemId, quantity, options? }] }, wobei quantity 1–99 ist, höchstens 50 Zeilen, und options ist [{ group, option }] (Gruppen- und Optionsschlüssel aus dem Menü). Preise, Optionsaufschläge und die Summe berechnet Delieta aus den Produkten der Firma, zu der der Schlüssel gehört, in ganzen PLN-Grosze. Ein Feld unitPrice / total / companyId im Body wird ignoriert. Die Adressen nach erfolgreicher und abgebrochener Zahlung stammen aus der Shop-Konfiguration dieser Firma, nie aus der Anfrage.
Ein Shop, der die Gästedaten selbst erfasst, ergänzt: fulfilment (delivery oder pickup, Standard delivery), requestedFor (ein Zeitpunkt aus GET /api/v1/checkout/slots als ISO-Datum mit Offset oder null = so schnell wie möglich), customer (name, phone, email?), address (street, number, flat?, postalCode im Format 00-000, city, note?), invoice (nip mit Prüfsumme, companyName?, address?), note (bis 500 Zeichen), locale und payment (blik, card, p24 oder cash). Regeln: Abholung, Barzahlung und eine Adresse verlangen customer; Lieferung mit customer verlangt address; eine Abhol-Bestellung verwirft die Adresse. Das optionale expectedTotalGrosze ist der Betrag, den der Client auf dem Bildschirm sah. Der Server rechnet nie damit, er vergleicht ihn nur mit dem jetzt berechneten Betrag: weichen sie ab, antwortet er 409 total_changed. items allein ist das alte Format: Online-Zahlung, Lieferung, und Stripe fragt selbst nach Telefon und Adresse.
POST /api/v1/checkout
Authorization: Bearer dlt_live_…
Content-Type: application/json
Idempotency-Key: chk-2026-08-26-42
{ "items": [{ "itemId": "i1", "quantity": 2 }] }
Antwort 201 (Kasse)
{
"url": "https://checkout.stripe.com/c/pay/cs_test_…",
"totalGrosze": 6400,
"currency": "PLN",
"pricingSource": "catalog"
}
Eine Online-Zahlung antwortet mit url: Leiten Sie den Gast auf die Zahlungsseite um. Die Bestellung entsteht, wenn er zahlt; das Ergebnis lesen Sie in GET /api/v1/checkout/result. Eine Barzahlung (payment: "cash") antwortet sofort mit einer aufgegebenen, noch unbezahlten Bestellung, die auf die Annahme durch die Küche wartet:
{
"orderShortId": "a1b2c3d4",
"trackingUrl": "https://delieta.pl/o/a1b2c3d4",
"totalGrosze": 6400,
"currency": "PLN",
"pricingSource": "catalog"
}
Geben Sie dem Gast die trackingUrl und fragen Sie den Status über GET /api/v1/orders/{id}/status ab. Barzahlung braucht kein Konto für Online-Zahlungen, aber customer. Derselbe Idempotency-Key liefert dieselbe Bestellung; eine wiederholte aufgegebene Barbestellung antwortet mit dieser Bestellung, bevor irgendeine Bedingung geprüft wird.
Ablehnungen: Bestellmodul aus → 403 module_disabled. Unbekannter oder nicht verfügbarer Artikel → 422 unprocessable (errors[].code: product_unknown, product_unavailable). Unbekannte Option, fehlende Pflichtgruppe oder zu viele Auswahlen → 422 option_invalid. Warenkorb unter dem Mindestwert des Shops → 422 below_minimum. Ein Artikel „Tylko w karcie“ → 422 product_menu_only. Ein Artikel außerhalb seiner Verfügbarkeitszeiten → 409 product_outside_hours. Lieferung an eine Adresse außerhalb des Gebiets → 409 out_of_range. Eine nicht gefundene Adresse → 422 address_not_found. Vorübergehend kein Lieferpreis → 503 delivery_quote_unavailable. Lieferung im Lokal nicht eingestellt → 409 delivery_unconfigured. Ein geänderter Betrag → 409 total_changed. Geschlossen und kein „so schnell wie möglich“ → 409 shop_closed. Zeit außerhalb der Liste → 409 slot_unavailable. Eine Übergabe, die der Shop nicht anbietet → 409 fulfilment_unavailable. Eine Zahlung, die der Shop nicht akzeptiert → 409 payment_unavailable. Online-Zahlung in einem Shop mit Beispieldaten bei Live-Zahlungen → 409 sample_mode (eine Vorschau zahlt nur im Testmodus). Kein Shop-Origin → 409 shop_unconfigured. Konto für Online-Zahlungen nicht bereit → 409 connect_not_ready (es gibt keine Schein-Zahlung).
GET /api/v1/checkout/slots
Wann der Shop eine Bestellung annimmt: „so schnell wie möglich“ und die Zeitpunkte, die ein Gast wählen kann. Scope catalog:read. Der Parameter fulfilment ist delivery (Standard) oder pickup. Die Zeiten werden aus Öffnungszeiten, geschlossenen Tagen, Vorbereitungszeit, Schritt und Horizont des Shops in Europe/Warsaw-Zeit berechnet und als ISO-Zeitpunkte geliefert. Die Kasse prüft kurz darauf dieselben Zeiten, daher endet eine Zeit außerhalb der Liste in 409 slot_unavailable. Nie gecacht. Ohne gesetzte Zeiten nimmt der Shop „so schnell wie möglich“ an, hat aber kein Zeitraster.
GET /api/v1/checkout/slots?fulfilment=pickup
Authorization: Bearer dlt_live_…
{
"asap": { "available": true, "readyInMinutes": 30 },
"slots": [
{ "startsAt": "2026-08-26T08:30:00.000Z" },
{ "startsAt": "2026-08-26T08:45:00.000Z" }
]
}
Ein ungültiges fulfilment → 400 invalid_request. Ein Shop, der diese Übergabe nicht anbietet → 409 fulfilment_unavailable. Bestellmodul aus → 403 module_disabled.
GET /api/v1/checkout/result
Das geprüfte Ergebnis einer Online-Zahlung, für die Bestätigungsseite des Shops. Scope checkout:create. Übergeben Sie die session_id aus der Rücksprungadresse (cs_test_… oder cs_live_…; die Zahlung hängt sie an die Erfolgsadresse an). Die Antwort stammt aus Bestellung und Zahlung in Delieta, nicht aus dem Browser: paid, processing (das Geld ist unterwegs oder die Bestellung wird noch gespeichert; in ein paar Sekunden erneut fragen), unpaid (der Gast ist gegangen oder die Sitzung ist abgelaufen) oder unknown (das ist keine Kasse dieses Shops). statementDescriptor ist der Text, den der Kunde auf dem Kontoauszug sieht; er ist null, wenn es keine Kartenzahlung ist. method ist blik, card, p24, cash, other oder null. Fehlende oder falsche session_id → 400 invalid_request. Nie gecacht.
GET /api/v1/checkout/result?session_id=cs_live_…
Authorization: Bearer dlt_live_…
{
"status": "paid",
"orderShortId": "K7M2QX",
"trackingUrl": "https://bella.delieta.pl/o/K7M2QX",
"totalGrosze": 5400,
"merchantName": "Bella Pizza",
"statementDescriptor": "BELLA* 3F9A12CD",
"method": "blik",
"livemode": true
}
POST /api/v1/checkout/quote
Ein Preisangebot für den Warenkorb, ohne Bestellung anzulegen: Gerichte, Verpackung, Lieferung und Summe. Scope checkout:create (veröffentlichbarer Schlüssel). Body: { items, fulfilment?, address? }, wie bei POST /checkout, ohne Gast- und Zahlungsdaten; Geldfelder werden ignoriert. Limit: 60 Angebote pro Minute und Besucher (rate_limited), getrennt von Menü-Lesezugriffen: jede neue Adresse ist ein kostenpflichtiges Geocoding. Der Shop berechnet Gebühren nicht selbst, er zeigt, was das Angebot liefert.
Antwort 200:
{
"quote": {
"currency": "PLN",
"itemsGrosze": 4500,
"packagingGrosze": 200,
"packaging": [{ "kind": "item", "label": "Opakowanie", "quantity": 2, "unitGrosze": 100, "totalGrosze": 200 }],
"delivery": { "status": "priced", "feeGrosze": 500, "free": false, "freeFromGrosze": 9000, "missingForFreeGrosze": 4500, "distanceMeters": 3200 },
"totalGrosze": 5200,
"minimum": { "minimumOrderGrosze": 3000, "missingGrosze": 0 }
}
}
delivery.status ist none (Abholung), needs_address (Kilometerstufen und noch keine Adresse) oder priced. Bei priced kommen feeGrosze und, wenn der Preis von der Entfernung abhängt, distanceMeters hinzu. Die Felder fromGrosze und toGrosze beschreiben den Bereich der Gebühr bei entfernungsabhängigem Preis. Das Feld packaging[].kind ist item (Gebühr pro Stück) oder order (Gebühr pro Bestellung). freeFromGrosze und missingForFreeGrosze dienen dem Hinweis „bis zur kostenlosen Lieferung fehlen …“. totalGrosze ist null, solange eine Adresse nötig ist.
Unter dem Mindestwert ist das Angebot kein Fehler: die Lücke zeigt minimum.missingGrosze. Der Fehler erscheint erst in POST /checkout als below_minimum. Die Entfernung ist die Luftlinie vom Lokal, aufgerundet auf 100 m; kostenlose Lieferung und Mindestbestellwert zählen nur die Gerichte, ohne Verpackung und Lieferung (genau die Schwelle ist kostenlos). Ablehnungen des Angebots: out_of_range, address_not_found, delivery_quote_unavailable (Abholung wird weiter berechnet), delivery_unconfigured, fulfilment_unavailable. Das Angebot prüft „Tylko w karcie“ und Verfügbarkeitszeiten (product_menu_only, product_outside_hours) nicht: das macht der Checkout zur Erfüllungszeit. Die Codes stehen in der Fehlertabelle. Preisregeln: Shop.
POST /api/v1/quotes
Eine Fahrtschätzung, bevor eine Bestellung existiert. Scope eta:quote. Der Start ist der Standort der Firma; Sie geben nur das Ziel an. Das Ergebnis ist immer eine Spanne (minMinutes / maxMinutes). Das Feld guaranteed ist die Konstante false — das ist kein SLA. Nach expiresAt erneut fragen.
{ "destination": { "lat": 53.123456, "lng": 18.012345 } }
{
"quote": {
"currency": "PLN",
"minMinutes": 35,
"maxMinutes": 50,
"guaranteed": false,
"breakdown": {
"prepMinutes": 15,
"loadMinutes": 4,
"travelMinutes": 12,
"padMinutes": 8
},
"distanceMeters": 4200,
"computedAt": "2026-08-26T12:00:00.000Z",
"expiresAt": "2026-08-26T12:05:00.000Z"
}
}
Zeigen Sie beide Enden des Bereichs. Eine einzelne Zahl in der Partner-UI führt in die Irre. Keine Standortkoordinaten → 409 venue_not_configured. Ziel außerhalb des Lieferradius der Firma → 409 out_of_range. Schlechter Body → 400 invalid_request. Die Firma, zu der der Schlüssel gehörte, existiert nicht → 404 tenant_not_found.
POST /api/v1/orders
Legt eine Bestellung für die Firma an, zu der der Schlüssel gehört. Scope orders:write. Der Server summiert die Zeilen und verwirft eine von Ihnen übergebene Summe. Die Einzelpreise stammen vom Aufrufer: pricingSource ist "caller". Das ist eine Bestellung aus einem Partnerkanal, kein Shop-Warenkorb; ein nach Katalog bepreister Warenkorb ist POST /api/v1/checkout.
Idempotency-Key
Ein Pflicht-Header, 8–255 Zeichen nach dem Entfernen der Leerzeichen. Ein Schlüssel pro logischem Versuch. Wiederholen Sie mit demselben Schlüssel und demselben Body nach einem Netzwerkfehler, 500 create_failed oder 503 idempotency_unavailable. Eine erfolgreiche 201-Antwort wird 24 Stunden gespeichert: Eine Wiederholung liefert dieselbe Antwort und Idempotent-Replayed: true. Derselbe Schlüssel mit anderem Body → 409 idempotency_key_conflict. Ist der Idempotenz-Speicher nicht verfügbar, bekommen Sie 503, nie ein Duplikat.
Body
Es gibt kein Firmenfeld. Ein typischer Aufruf mit den Preisen des Aufrufers:
{
"customerName": "Jan Kowalski",
"customerPhone": "+48123456789",
"destinationAddress": "ul. Przykładowa 1, 85-000 Bydgoszcz",
"destinationPoint": { "lat": 53.123456, "lng": 18.012345 },
"items": [
{ "name": "Margherita 32cm", "qty": 1, "unitPrice": 3200 },
{ "name": "Cola 0,5l", "qty": 2, "unitPrice": 800 }
],
"paymentMethod": "cash",
"channel": "web",
"externalRef": "your-storefront-order-42"
}
| Feld | Pflicht | Hinweise |
|---|---|---|
customerName | ja | 1–200 Zeichen |
customerPhone | ja | 1–40 Zeichen |
destinationAddress | ja | 1–500 Zeichen |
destinationPoint | nein | { lat, lng } WGS84. Nicht destination oder destinationCoordinates — das ist 400 invalid_request mit errors[].code unknown_field, weil eine Bestellung ohne Koordinaten keine ETA bekommt |
items | ja | 1–50 Zeilen; qty 1–99 |
items[].name / items[].unitPrice | ja | Grosze; der Server berechnet totalGrosze neu |
items[].menuItemId | nein | Produkt-ID; options wird bei der Preisbildung ignoriert |
details | nein | bis 4000 Zeichen |
customerNote | nein | bis 2000 Zeichen |
paymentMethod | nein | cash | card | online (Standard cash) |
channel | nein | phone | web | pos | integration (Standard web) |
scheduledFor | nein | ein Zeitpunkt als ISO-Datum |
sourceVariant | nein | bis 64 Zeichen |
externalRef | nein | bis 64 Zeichen; Dedup auf Ihrer Seite, eine Kollision → duplicate_external_ref |
POST /api/v1/orders
Authorization: Bearer dlt_live_…
Content-Type: application/json
Idempotency-Key: ord-2026-08-26-storefront-42
Antwort 201 (Bestellung)
{
"order": {
"id": "1",
"shortId": "a1b2c3d4",
"status": "accepted",
"createdAt": "2026-08-26T12:00:00.000Z",
"tenantSlug": "bella-pizza",
"externalRef": "your-storefront-order-42",
"currency": "PLN",
"totalGrosze": 4800,
"lines": [
{ "name": "Margherita 32cm", "qty": 1, "unitPrice": 3200 },
{ "name": "Cola 0,5l", "qty": 2, "unitPrice": 800 }
],
"pricingSource": "caller",
"trackingUrl": "https://delieta.pl/o/a1b2c3d4"
}
}
status ist accepted oder pending_acceptance, je nach Annahmemodus der Firma. Die trackingUrl geben Sie dem Kunden. Sie ist die einzige öffentliche Tracking-Adresse. Schreibfehler: 404 tenant_not_found, 409 duplicate_external_ref, 500 create_failed.
GET /api/v1/orders/
Liest eine Bestellung der Firma, zu der der Schlüssel gehört. Der Scope ist derselbe: orders:write — die Scope-Menge ist geschlossen und auf aktiven Schlüsseln bereits ausgegeben; ein neues orders:read würde bestehende Integrationen brechen. Das Abfragen zählt zum Lese-Limit, nicht zum Anlege-Limit.
id ist die order.id aus der 201-Antwort. Eine fremde oder nicht existierende ID → 404 not_found, die Antwort verrät also nicht, ob so eine Bestellung existiert. Die Antwort hat weniger Felder als die beim Anlegen. Der Partner bekommt den Bestellverlauf, keine internen Spalten:
{
"order": {
"id": "1",
"shortId": "a1b2c3d4",
"status": "pending_acceptance",
"externalRef": "your-storefront-order-42",
"createdAt": "2026-08-26T12:00:00.000Z",
"promisedAt": null,
"acceptedAt": null,
"rejectedAt": null,
"rejectionReason": null,
"acceptanceDeadlineAt": 1756200000000
}
}
acceptedAt, rejectedAt und acceptanceDeadlineAt sind Unix-ms oder null. Die Status, die Sie sehen, sind die aufeinanderfolgenden Bestellstatus: pending_acceptance, accepted, preparing, ready, on_the_way, delivered und rejected. Die Lieferung überspringt ready, die Abholung überspringt on_the_way.
GET /api/v1/orders//status
Wo die Bestellung ist, für die Tracking-Seite des Gastes. Scope catalog:read, ein veröffentlichbarer Schlüssel kann das also aus einem Shop abfragen. {id} ist der kurze Bestellcode (orderShortId aus der Kassenantwort, Groß-/Kleinschreibung egal), nicht order.id. Die Antwort enthält keine Kundendaten: nur Status, Übergabeart und Zeit. Ein nicht existierender, falsch geschriebener oder fremder Code antwortet gleich: 404 not_found.
GET /api/v1/orders/a1b2c3d4/status
Authorization: Bearer dlt_live_…
{
"status": "preparing",
"fulfilment": "pickup",
"etaMinutes": 12,
"promisedAt": "2026-08-26T08:45:00.000Z"
}
status ist einer von: pending_acceptance, accepted, preparing, ready (Abholung: „Abholbereit“), on_the_way, delivered, rejected. etaMinutes sind ganze Minuten bis promisedAt, nie negativ, und erscheint nur, solange die Küche die Bestellung hat (accepted, preparing, on_the_way). promisedAt ist die vom Personal versprochene Zeit, ohne sie die vom Gast gewünschte Zeit. readyAt erscheint, wenn eine Abhol-Bestellung ready ist.
Tischreservierungen
Sie brauchen das eingeschaltete Reservierungsmodul (Tarif oder Zuteilung und der Schalter des Inhabers). Modul aus: 403 module_disabled.
GET /api/v1/floor-plan
GET /api/v1/floor-plan
Zonen und Tische der Firma mit Geometrie (grid ist ein einheitenloses { width, height }-Raster, das der Shop auf seine Zeichnung skaliert). Scope catalog:read. ?locale= übersetzt Zonennamen. Die Antwort enthält x-delieta-cache-tag.
GET /api/v1/reservations/availability
GET /api/v1/reservations/availability?date=2030-06-14&partySize=2
Freie Startzeiten und für jede die freien Tische, die zur Gruppe passen: date, partySize, durationMinutes und slots[] mit startsAt, time und tableIds. time ist die Ortszeit in Europe/Warsaw, startsAt der zu buchende Zeitpunkt. Gibt es keine Zeiten, sagt das Feld closedReason warum: closed_day, closure oder hours_not_configured. Ein falsches date-Format oder partySize außerhalb 1–100: 422 validation_failed. Scope catalog:read. Nie gecacht.
POST /api/v1/reservations
POST /api/v1/reservations
Authorization: Bearer dlt_live_…
Idempotency-Key: 4b0b3f0e-6d21-4f0a-9c1c-0d6f0e0a7a11
Content-Type: application/json
{ "startsAt": "2030-06-14T16:00:00.000Z", "partySize": 2, "tableId": "…",
"guest": { "name": "Ala Kowalska", "phone": "600100200", "email": "ala@example.pl" },
"occasion": "birthday", "note": "przy oknie" }
Felder: startsAt (ein ISO-Datum mit Offset), partySize (1–100), tableId?, guest (name bis 80 Zeichen, phone, email?), occasion? (none, birthday, anniversary, date, business), note? (bis 500 Zeichen), locale? (pl, en, de, uk — in dieser Sprache geht die Bestätigung an den Gast). Ein Body über 8 KiB → 422 validation_failed.
Zwei Schlüsselarten. Ein veröffentlichbarer Schlüssel mit checkout:create macht eine web-Buchung: Der Start muss ein angebotener Zeitpunkt sein, der Status ist requested oder bei automatischer Bestätigung confirmed. Ein geheimer Schlüssel mit reservations:write macht eine api-Buchung aus dem eigenen System: zu jeder Stunde, der Tisch muss nur frei sein, der Status ist immer confirmed. Idempotency-Key (8–100 Zeichen) ist optional, wird aber beachtet: Derselbe Schlüssel liefert die erste Buchung. Das Feld website ist eine Bot-Falle, lassen Sie es leer.
201 liefert id, reference, status, startsAt, durationMinutes, tableIds. status ist requested, confirmed, seated, completed, cancelled oder no_show. Ablehnungen sind problem+json mit stabilem code:
| Status | code |
|---|---|
409 | slot_unavailable, table_taken |
422 | validation_failed, party_too_large, out_of_range |
403 | module_disabled, scope_required |
GET /api/v1/events
Die Veranstaltungen der Firma: Workshops, Verkostungen, Abende. Scope catalog:read. Braucht das Modul Veranstaltungen: aus → 403 module_disabled, nicht lesbar → 503 module_unavailable. Optional ?locale=.
Antwort 200: { "events": [...], "locale": "pl", "generatedAt": "..." }. Die Liste enthält nur veröffentlichte Veranstaltungen mit kommendem Termin, in der Reihenfolge sort, dann nach dem nächsten Termin. Jede Veranstaltung:
| Feld | Bedeutung |
|---|---|
id, slug, name, description, audience, scheduleNote, imageUrl | die Beschreibung der Veranstaltung; description ist reiner Text, Zeilen mit - sind Aufzählungspunkte |
priceMode, pricePerPersonGrosze | per_person, free oder on_request; der Preis nur bei per_person |
participants | { min, max } pro Anfrage |
scheduleMode, occurrences | dated oder on_request („termin do uzgodnienia“, leere occurrences); Termine { id, date, time, endTime, note } |
booking, phone | inquiry oder phone; das Telefon der Veranstaltung oder der Firma. Bei phone die Nummer zeigen: die API lehnt Anmeldungen ab (event_phone_only) |
Die Antwort enthält den Header x-delieta-cache-tag (Tag tenant:{id}:events, alle 5 Minuten aufgefrischt), und der Zeitfilter läuft nach dem Cache, sodass vergangene Termine ohne Warten verschwinden.
POST /api/v1/inquiries
Eine Gästeanfrage: Kontakt, Catering oder Veranstaltung. Scope checkout:create (veröffentlichbarer Schlüssel), also rufen Sie sie vom Server des Shops auf, nicht vom Browser. Das Limit gilt pro Besucher (übergeben Sie seine Adresse in x-delieta-visitor). Das Feld website ist eine Bot-Falle: leer lassen. Das nötige Modul hängt von der Art ab: catering und contact brauchen das Catering-Modul, event das Modul Veranstaltungen. Ist das nötige Modul aus, lautet die Antwort 403 module_disabled. Ein Body über 64 KiB → 413 payload_too_large. Ein unbekanntes Feld → 400 invalid_request.
Felder: kind (contact, catering oder event), name, phone und/oder email (eines davon ist Pflicht, bei Catering phone), message (Pflicht bei contact), topic? (reservation, catering, order, other), locale? und, für catering, ein Objekt catering: formatId?, headcount, eventDate (JJJJ-MM-TT), eventTime? (HH:MM), deliveryAddress?, dietary? (vegetarian, gluten_free, lactose_free), dietaryNote?, company?, nip?, venue? (onsite oder delivery) und addOns? ([{ key, quantity }], bis zu 30 Positionen, quantity 1–99, jeder key einmal). Der Body enthält nie den Preis oder den Namen eines Zusatzes: sie stammen aus dem Format. Zusätze dürfen nur mit formatId gesendet werden.
Für event sind die Felder ein Objekt event: eventId, occurrenceId? (ein Termin aus GET /events), preferredDate? und preferredTime? (für eine Veranstaltung „Do uzgodnienia“; preferredDate ist dann Pflicht), participants und organization?. phone ist Pflicht, message optional. Der Body ist streng: ein unbekanntes Feld ist 400.
POST /api/v1/inquiries
Authorization: Bearer dlt_live_…
Content-Type: application/json
{
"kind": "catering",
"name": "Anna Nowak",
"phone": "+48600100200",
"catering": { "formatId": "f1", "headcount": 40, "eventDate": "2026-12-12", "company": "Acme", "nip": "5260250995" }
}
Erfolg: 201 { "inquiry": { "reference": "AB12CD", "status": "new" } }. Eine falsche NIP (Prüfsumme), ein fehlendes Telefon oder ein unbekanntes Feld → 400 invalid_request mit einer errors-Liste. Ein unbekanntes Format → 422 unprocessable. Eine Anfrage vor Ort für einen Tag, an dem das Lokal geschlossen ist → 422 venue_closed („W tym dniu lokal jest zamknięty.”).
Anmeldungen zu Veranstaltungen (kind: "event")
Eine Anmeldung zu einer Veranstaltung des Lokals (Modul Wydarzenia) ist ebenfalls eine Anfrage: kind: "event" mit einem event-Block. Eine Telefonnummer ist Pflicht. Eine Veranstaltung mit Terminen nimmt occurrenceId (einer der Termine aus GET /api/v1/events); eine Veranstaltung „termin do uzgodnienia“ nimmt preferredDate (optional preferredTime). Ist das Modul Wydarzenia aus, lautet die Antwort 403 module_disabled. Ablehnungen sind 422 unprocessable mit dem Code in errors[].code und polnischem Text am Feld: unknown_event, event_not_bookable, event_past, event_phone_only, unknown_occurrence, event_in_past, preferred_date_required, participants_out_of_range.
POST /api/v1/catering-orders
Die Firma erfasst einen Catering-Auftrag aus ihrem eigenen System. Scope catering:write (nur geheimer Schlüssel). Delieta erfasst den Verkauf: Der Body enthält den vereinbarten Betrag in Grosze (agreedAmountGrosze); die Zahlung ist optional (paid: true markiert bezahlt). Die Positionen (lines) verweisen per menuItemId auf Produkte der Firma; eine fremde oder unbekannte ID → 422 unprocessable. Der Auftrag hat source: api. Optionaler Idempotency-Key: eine Wiederholung liefert den ersten Auftrag (200, Idempotent-Replayed: true). Modul aus → 403 module_disabled. Eine belegte Verknüpfung mit einer Anfrage → 409 conflict. Ein Body über 64 KiB → 413 payload_too_large.
Felder: customer (name, phone?, email?, company?, nip?), eventDate (JJJJ-MM-TT), eventTime?, headcount?, deliveryAddress?, formatId?, lines[] (menuItemId, quantity, unitPriceGrosze?), agreedAmountGrosze, note?, status? (planned, confirmed, delivered, cancelled), paid?, inquiryId?.
{
"customer": { "name": "Acme sp. z o.o.", "nip": "5260250995" },
"eventDate": "2026-12-12",
"headcount": 40,
"lines": [{ "menuItemId": "i1", "quantity": 40 }],
"agreedAmountGrosze": 240000
}
Erfolg: 201 { "order": { "id", "reference", "status": "planned", "source": "api", "paymentStatus": "unpaid", … } }. paymentStatus ist unpaid oder paid.
GET /api/v1/catering-orders
GET /api/v1/catering-orders?page=1&limit=50 liefert die Liste { orders, page, limit, total }, neueste zuerst (limit bis 100). Derselbe Scope catering:write.
Die Catering-Formate der Firma stehen in der Antwort der Shop-Konfiguration, nur bei eingeschaltetem Modul: cateringFormats (Lieferformate mit „ab“-Preis) und cateringPackages (alle Formate mit ihren Paketfeldern). Jedes Format hat imageUrl (Foto-URL oder null).
OpenAPI
GET /api/v1/openapi
Das öffentliche OpenAPI-3.1-Dokument, ohne Schlüssel. Erzeugen Sie daraus einen Client, wenn Sie kein handgeschriebenes curl wollen. Diese Seite wird damit abgeglichen — weichen sie ab, haben OpenAPI und der Test im Repository recht.
Es gibt keine Webhook-Operation von Delieta für den Bestellstatus. Den Status lesen Sie hier, per GET. Die einzige Kommunikation in die andere Richtung (von Delieta an den Shop) beschreibt Webhooks.

