ENTWICKLER · API v1
Eine App für DigiMenu entwickeln
Beantragen Sie API-Zugang und beschreiben Sie Ihren Anwendungsfall. Nach der Freigabe durch DigiMenu können Sie sich mit Zustimmung des Restaurants verbinden. Dieser Leitfaden erklärt Antrag, Einrichtung und technische Details.
So funktionieren Apps
Für jede App sind ein Antrag und die Freigabe durch DigiMenu erforderlich. Beschreiben Sie den Zweck, den Bedarf an API-Zugriff und die verwendeten Daten. Vor der Freigabe sind Installation, Tokens und Webhooks auch im TEST-Modus gesperrt. Ein Abonnement oder Zugang zum Entwicklerportal ersetzt die Freigabe nicht.
Ihre App läuft auf Ihrem eigenen Server. Ein Restaurantadministrator erteilt in DigiMenu die Berechtigung. Ihre App greift mit dem OAuth-Token dieser Installation auf die API zu und empfängt signierte Webhooks. Sie laden weder Code noch ZIP-Dateien auf DigiMenu-Server hoch.
API-Basis: https://api.digimenu.us/api/v1. TEST- und LIVE-Apps verwenden denselben Host. TEST beschränkt die Installation auf die Entwicklerorganisation; es ist keine separate oder simulierte Datenbank. Verwenden Sie ein eigenes Testrestaurant.
- Lesen Sie Menüs und Tische, rufen Sie Bestellungen ab und entwickeln Sie Lieferintegrationen.
- Diese API bietet keine Endpunkte zum Schreiben von Menüs oder Produkten, Erstellen von Bestellungen, Verwalten von Lagerbeständen oder CRM-Daten, Einziehen von Zahlungen oder Erstatten von Beträgen. Nicht alle Funktionen der DigiMenu-Apps sind für Drittanbieter verfügbar.
- Die Beispiel-App läuft mit Node.js 22.18+ ohne zusätzliche Pakete. Sie enthält OAuth-Verbindung, Menüabruf, Token-Erneuerung und Empfang signierter Webhooks.
Unabhängiges Entwicklerkonto
Beantragen Sie unter developers.digimenu.us Zugang ohne Restaurantkonto oder Abo. Bestätigen Sie Ihre E-Mail und beschreiben Sie sich oder Ihr Unternehmen, den App-Zweck und Datenbedarf. Nach Entwicklerfreigabe können Sie einen App-Antrag erstellen. Jede App wird separat geprüft; die Kontofreigabe allein gewährt keinen API-Zugriff.
Erstellen Sie in den Einstellungen einer genehmigten App eine isolierte Testumgebung. Sie nutzt keine Kundentabellen und unterstützt Beispielmenüs, Tische, Bestellabfragen und Lieferstatuswechsel. OAuth, Kurier- und Webhook-Abläufe werden nicht simuliert. Der sandbox_-Schlüssel gilt 24 Stunden und funktioniert nicht an der Live-Adresse /api/v1. Zurücksetzen stellt Testdaten wieder her und widerruft den alten Schlüssel.
GET https://api.digimenu.us/api/admin/developer-sandbox/v1/menu
Authorization: Bearer SANDBOX_TOKENSchalten Sie Ihre genehmigte App für den Produktivbetrieb auf LIVE. Ein Restaurantadministrator muss über den folgenden OAuth-Ablauf separat zustimmen. Das Entwicklerkonto gewährt keinen Zugang zum Restaurant-Dashboard. Eine Kontosperre blockiert auch bestehende Apps und Testschlüssel.
App registrieren und testen
Öffnen Sie developers.digimenu.us mit Ihrem genehmigten unabhängigen Konto. Entwickler mit Geschäftskonto können weiterhin panel.digimenu.us/gelistirici nutzen; dafür muss Developer & Platform in ihrer Organisation aktiviert sein. Die folgenden App-Einstellungen gelten für beide Portale.
- Erstellen Sie einen Entwurf für den API-Zugang. Beschreiben Sie Zweck und Datenbedarf in 40–2000 Zeichen. Die erzeugte clientId gewährt allein keinen Zugriff.
- Registrieren Sie eine genaue HTTPS-Weiterleitungsadresse, etwa https://app.example/callback. Platzhalter, Zugangsdaten und URL-Fragmente werden abgelehnt. Auch abschließende Schrägstriche müssen übereinstimmen.
- Wählen Sie für das Beispiel menu:read, orders:read und webhooks:manage. Prüfen Sie die erteilten Berechtigungen in jeder Token-Antwort.
- Speichern Sie Zweck, Weiterleitungsadressen und Berechtigungen und reichen Sie die App zur Prüfung ein. Nach der DigiMenu-Freigabe können Sie optional ein client_secret erstellen. Sobald es eingerichtet ist, muss es beim Codeaustausch und bei der Token-Erneuerung mitgesendet werden. Es wird nur einmal angezeigt.
- Autorisieren Sie nach der Freigabe ein Testrestaurant Ihrer eigenen Organisation im TEST-Modus. Nutzen Sie für Callback- und Webhook-Adressen eine eigene HTTPS-Domain oder einen HTTPS-Tunnel. HTTP-Weiterleitungen an localhost können nicht registriert werden.
APP_ORIGIN=https://eklenti.example
CLIENT_ID=YOUR_CLIENT_ID
SCOPES="menu:read orders:read webhooks:manage"
# Only if a secret was created:
# CLIENT_SECRET=YOUR_CLIENT_SECRET
# Set after registering the webhook receiver:
# WEBHOOK_SECRET=YOUR_WEBHOOK_SECRET
PORT=8787
INBOX_DIR=./webhook-inboxnode --env-file=starter.env digimenu-starter.mjs
# HTTPS proxy/tunnel → http://127.0.0.1:8787
# Open https://eklenti.example/connectDas Beispiel dient der Entwicklung: Sitzungen und Tokens liegen im Arbeitsspeicher und erfordern nach einem Neustart eine erneute Autorisierung. Webhook-Ereignisse werden lokal gespeichert. Ergänzen Sie vor dem Produktivbetrieb Benutzeranmeldung, verschlüsselte dauerhafte Token-Speicherung, Zuordnung von Organisationen und Installationen, eine dauerhafte Aufgabenwarteschlange sowie eine Aufbewahrungsrichtlinie. Nehmen Sie starter.env nicht in Git auf.
OAuth und PKCE
PKCE S256 ist für alle Clients Pflicht. Erzeugen Sie für jeden Verbindungsversuch neue Werte für code_verifier und state und binden Sie diese an die serverseitige Benutzersitzung. Der Verifier muss 43–128 Zeichen lang sein. Prüfen Sie state vor dem Codeaustausch. Autorisierungscodes sind einmalig verwendbar und 60 Sekunden gültig.
import { randomBytes, createHash } from 'node:crypto';
const verifier = randomBytes(32).toString('base64url');
const state = randomBytes(32).toString('base64url');
const challenge = createHash('sha256').update(verifier).digest('base64url');
// Store verifier + state in this user's server session before redirecting.
const url = new URL('https://panel.digimenu.us/yetkilendir');
url.search = new URLSearchParams({
client_id: process.env.CLIENT_ID,
redirect_uri: 'https://eklenti.example/callback',
scope: 'menu:read orders:read webhooks:manage',
code_challenge: challenge,
code_challenge_method: 'S256',
state,
}).toString();Nach Zustimmung erhält der Callback code und state; bei Ablehnung error=access_denied und state. Erfassen Sie keine Anmeldetokens des Dashboards. App-Tokens erhalten Sie über den folgenden Austausch. Der Grant client_credentials wird nicht unterstützt.
POST https://api.digimenu.us/api/v1/oauth/token
Content-Type: application/json
{
"grant_type": "authorization_code",
"client_id": "YOUR_CLIENT_ID",
"code": "CODE_FROM_CALLBACK",
"redirect_uri": "https://eklenti.example/callback",
"code_verifier": "ORIGINAL_VERIFIER"
}{
"access_token": "ACCESS_TOKEN",
"refresh_token": "REFRESH_TOKEN",
"token_type": "Bearer",
"expires_in": 3600,
"scope": "menu:read orders:read webhooks:manage"
}Token-Erneuerung und Deinstallation
Access-Tokens gelten 1 Stunde, Refresh-Tokens 60 Tage. Maßgeblich ist expires_in. Jede Erneuerung liefert ein neues Paar aus Access- und Refresh-Token. Speichern Sie beide atomar und führen Sie Erneuerungen je Installation nacheinander aus. Die erneute Verwendung eines alten Refresh-Tokens widerruft die gesamte Refresh-Token-Familie der Installation.
POST https://api.digimenu.us/api/v1/oauth/token
Content-Type: application/json
{
"grant_type": "refresh_token",
"client_id": "YOUR_CLIENT_ID",
"refresh_token": "LATEST_REFRESH_TOKEN"
}Senden Sie client_secret mit, sofern es eingerichtet ist. Geht die Antwort auf eine Erneuerung verloren, versuchen Sie es nicht blind mit dem alten Token erneut, sondern verbinden Sie die App erneut per OAuth. Wiederholen Sie invalid_grant nicht in einer Schleife.
Die Deinstallation im Dashboard beendet den API-Zugriff. Stoppen Sie bei APP_REVOKED oder APP_NOT_INSTALLED die Synchronisierung und bitten Sie das Restaurant um erneute Autorisierung. SUBSCRIPTION_REQUIRED muss im Abrechnungsbereich des Restaurants gelöst werden; eine Token-Erneuerung hilft nicht.
Berechtigungen und Datenzugriff
Jedes Access-Token gehört zu genau einem Restaurant und einer Installation. Speichern Sie Berechtigungen und Tokens pro Restaurant getrennt, zugeordnet über installationId und restaurantId. Berechtigungen schließen einander nicht ein: orders:write enthält nicht orders:read.
- menu:read — Aktive Kategorien und Produkte; kein Schreibzugriff auf das Menü.
- orders:read — Bestellliste und Details; ohne Positionspreise, Zahlungsdatensätze oder Kundentabelle.
- orders:write — Nur Statuswechsel im Lieferabschnitt; keine Zahlungs-, Küchen- oder Stornierungsvorgänge.
- tables:read — ID, Name und Nummer aktiver Tische; keine QR-Schlüssel oder Belegungsdaten.
- deliveries:read / deliveries:write — Zugewiesene Kurierfahrten lesen und bearbeiten. Die write-Berechtigung legt einen Kurier für die Installation an.
- customers:read — Stellt keinen /customers-Endpunkt bereit. Detaillierte Bestelladressen erfordern diese Berechtigung UND die Zuweisung an den Kurier Ihrer App.
- webhooks:manage — Empfänger und Zustellprotokolle der App verwalten. Für den Empfang von Ereignissen ist zusätzlich orders:read oder deliveries:read erforderlich.
Anfragen, Seitennavigation und Wiederholungen
curl 'https://api.digimenu.us/api/v1/orders?channel=DELIVERY&limit=25' \
-H "Authorization: Bearer $ACCESS_TOKEN" \
-H 'Accept: application/json'Bestell- und Webhook-Zustelllisten verwenden standardmäßig limit=25; zulässig sind 1–100. Übergeben Sie nextCursor bei der nächsten Anfrage als cursor und beenden Sie bei null. /tables und /webhooks liefern direkt Arrays. /menu liefert restaurant/categories ohne umschließendes data-Objekt.
Datumsangaben verwenden ISO 8601. price im Menü steht in der Hauptwährungseinheit (149.90), deliveryFeeMinor in der Untereinheit (14990). Die Antworten enthalten keine Währung; ermitteln Sie die Währung des Restaurants gesondert. null kann fehlende oder nicht anwendbare Daten bedeuten.
Idempotency-Key gilt nur für /orders/{id}/transitions: ein installationsbezogener Antwortcache im Prozessspeicher mit fünf Minuten Gültigkeit. Verwenden Sie für jede Kombination aus Bestellung, Vorgang und Inhalt einen anderen Schlüssel. Der Cache überlebt keine Neustarts und garantiert keinen Wiederholungsschutz für alle POST-Anfragen. Prüfen Sie bei unklarem Netzwerkausfall den aktuellen Zustand, bevor Sie andere Schreibvorgänge wiederholen.
Webhooks einrichten und prüfen
Empfänger gehören zur App, nicht zum Restaurant. Registrieren Sie nicht für jede Installation einen neuen Empfänger. Verwenden Sie eine öffentliche HTTPS-Adresse auf Port 443. Private oder lokale Hosts und Weiterleitungen werden abgelehnt. Prüfen Sie die Signatur anhand der unveränderten Anfragebytes, bevor Sie JSON parsen.
POST https://api.digimenu.us/api/v1/webhooks
Authorization: Bearer ACCESS_TOKEN
Content-Type: application/json
{
"url": "https://eklenti.example/webhooks",
"events": ["order.created", "order.paid"]
}Die Antwort 201 zeigt secret nur einmal an. Speichern Sie es sicher. Die Empfängererstellung ist nicht idempotent: Prüfen Sie nach einer verlorenen Antwort zuerst GET /webhooks. Ist der Schlüssel verloren, deaktivieren Sie den alten Empfänger und erstellen Sie einen neuen.
X-Digimenu-Signature: t=1790467200,v1=<64-character-hex-hmac>
X-Digimenu-Event-Id: <event-uuid>
X-Digimenu-Event-Type: order.created
HMAC-SHA256(secret, timestamp + "." + rawBody){
"id": "00000000-0000-4000-8000-000000000001",
"type": "order.created",
"createdAt": "2026-09-27T00:00:00.000Z",
"restaurantId": "restaurant_example",
"installationId": "installation_example",
"data": { "orderId": "order_example", "channel": "TAKEAWAY" }
}Vergleichen Sie Signaturen in konstanter Zeit und lehnen Sie Zeitstempel außerhalb von ±300 Sekunden ab. Die id im signierten Inhalt muss X-Digimenu-Event-Id entsprechen. Speichern Sie diese ID dauerhaft zur Duplikaterkennung und gleichen Sie restaurantId/installationId mit Ihren Installationsdatensätzen ab.
Antworten Sie innerhalb von 10 Sekunden mit 2xx, nachdem das Ereignis dauerhaft gespeichert wurde. Fehlgeschlagene Zustellungen werden insgesamt bis zu 6-mal versucht, mit Wartezeiten von 30, 120, 600, 1.800 und 7.200 Sekunden vor den Wiederholungen. Die Verarbeitung kann diese Zeiten verlängern. Insgesamt 20 Zustellprotokolle mit FAILED deaktivieren den Empfänger automatisch.
Reihenfolge und genau einmalige Zustellung sind nicht garantiert. Lesen Sie den aktuellen Bestellstatus mit GET /orders/{id}. Prüfen Sie Versuche unter GET /webhooks/{id}/deliveries. Es gibt keinen öffentlichen Endpunkt für erneute Zustellung oder Reaktivierung.
Ereignisse
- order.created — Abhol- oder Lieferbestellung erstellt. data: orderId, channel. Erfordert orders:read; nicht für jeden Ablauf einer Tischbestellung garantiert.
- order.paid — Zahlung einer Kanalbestellung abgeschlossen. data: orderId. Erfordert orders:read.
- delivery.ready — Zuweisung an ein externes Kuriernetz. data: orderId, runId und Lieferdetails. Erfordert deliveries:read. Prüfen Sie die zugewiesene Fahrt über die API und protokollieren Sie keine sensiblen Lieferdaten.
- delivery.picked_up / delivery.delivered / delivery.failed — data: orderId, runId, status. Erfordert deliveries:read.
- delivery.assigned — Reservierter Name, der bei der Registrierung akzeptiert wird. In der aktuellen Version wird dieses Ereignis nicht erzeugt. Machen Sie Ihren Ablauf nicht davon abhängig.
Kurierintegration
Installieren Sie mit deliveries:read, deliveries:write und webhooks:manage. Ergänzen Sie orders:read für Bestelldetails und customers:read für Kontaktdaten zugewiesener Bestellungen. Nach der Lieferzuweisung durch das Restaurant lesen Sie die Fahrt anhand der runId aus delivery.ready.
GET /api/v1/deliveries/{runId}
POST /api/v1/deliveries/{runId}/depart
POST /api/v1/deliveries/{runId}/pings
{ "lat": 41.01, "lng": 28.97 }
POST /api/v1/deliveries/orders/{orderId}/delivered
{ "code": "CUSTOMER_DELIVERY_CODE" }
# Alternative when delivery fails:
POST /api/v1/deliveries/orders/{orderId}/failed
{ "reason": "CUSTOMER_ABSENT" }Die Lieferbestätigung prüft einen aktuellen Standort, die Nähe zur Adresse und den gewählten Nachweismodus des Restaurants. CODE verlangt den Kundencode. PHOTO/SIGNATURE benötigen einen von DigiMenu erzeugten Upload-Datensatz; die öffentliche v1 bietet jedoch keinen Endpunkt für Nachweis-Uploads. Diese Modi lassen sich daher nicht vollständig allein über v1 abbilden.
Kurier-Apps sollten /deliveries verwenden. Der allgemeine Pfad /orders/{id}/transitions führt keine GPS- oder Zustellnachweisprüfung durch. pings ermittelt die aktive Fahrt über den authentifizierten Kurier; runId kann keine andere Fahrt auswählen.
Eingebettete Ansichten
Das Entwicklerportal bietet embeddedUrl und embeddedOrigin. Die Ansicht läuft in einem iframe mit Sandbox. Verlassen Sie sich nicht auf Cookies, Sitzungszugriff oder das DOM der übergeordneten Seite. Tokens werden nie per iframe-Nachricht übertragen. Beginnen Sie mit einer eigenständigen HTTPS-App.
Die Schnittstelle für eingebettete Ansichten ist noch kein stabiler Integrationsvertrag. Die aktuelle iframe-Sandbox und originbezogene Nachrichten müssen gemeinsam durchgängig geprüft werden. Setzen Sie nicht voraus, dass restaurantId/locale/theme oder Nachrichten zur automatischen Höhenanpassung eintreffen. Testen Sie diesen Ablauf vor dem Produktivbetrieb mit dem Support.
Fehler und Grenzen
Pro Installation gelten 30 Anfragen je 10 Sekunden und 5.000 je UTC-Stundenfenster. Zusätzlich gelten allgemein 600 Anfragen pro Minute und IP, am Token-Endpunkt 30 pro Minute und IP. Verwenden Sie nach 429 zunehmende Wartezeiten mit Zufallsanteil. Gehen Sie nicht davon aus, dass Retry-After immer vorhanden ist.
{ "code": "insufficient_scope", "requiredScope": "orders:read", "message": "..." }{ "error": "invalid_grant", "error_description": "..." }- 400 — Prüfen Sie Datentypen, erlaubte Statuswerte und Pflichtfelder. Unbekannte Felder im Anfrageinhalt werden abgelehnt. Validierungsfehler können auch statusCode/message/error verwenden.
- 401 — Erneuern Sie bei APP_UNAUTHENTICATED ein abgelaufenes Token. APP_REVOKED / APP_NOT_INSTALLED erfordern eine neue Autorisierung. Prüfen Sie bei OAuth invalid_client die Werte client_id und das eingerichtete client_secret.
- 403 — insufficient_scope, SUBSCRIPTION_REQUIRED oder eine Kurier-/Nachweisbeschränkung. Wiederholte Anfragen beheben keine fehlende Berechtigung.
- 404 — Die Ressource fehlt oder liegt außerhalb des Restaurant-/Kurierzugriffs des Tokens.
- 409 — INVALID_TRANSITION: Lesen Sie den aktuellen Bestellstatus, bevor Sie den nächsten Übergang wählen.
- 429 — rate_limited / quota_exceeded oder allgemeine Drosselung. Verlangsamen Sie die Warteschlange; bei Stundenquoten müssen Sie das nächste UTC-Stundenfenster abwarten.
- 5xx oder Netzwerkausfall — Wiederholen Sie Leseanfragen nur begrenzt. Prüfen Sie vor der Wiederholung von Schreibvorgängen deren Ergebnis.
Prüfung und Veröffentlichung
Apps beginnen als DRAFT und TEST. Weiterleitungsadressen, Berechtigungen und Beschreibung lassen sich in DRAFT oder REJECTED bearbeiten. Registrieren Sie mindestens eine Weiterleitung und eine Berechtigung und reichen Sie die App als IN_REVIEW ein. Nach Freigabe lautet der Status APPROVED. Wechseln Sie anschließend über die Veröffentlichung in den LIVE-Modus. Wenden Sie sich für spätere Konfigurationsänderungen einer freigegebenen App an den Support.
- Testen Sie Zustimmung, Ablehnung, falschen state und einen abgelaufenen Autorisierungscode.
- Prüfen Sie, dass Tokens und Datensätze zweier Testrestaurants getrennt bleiben.
- Testen Sie Token-Erneuerung, eine verlorene Antwort und die Deinstallation im Dashboard.
- Testen Sie manipulierte, abgelaufene, doppelte und in falscher Reihenfolge eintreffende Webhooks.
- Begrenzen Sie Wiederholungen bei 429 und vorübergehenden Fehlern und behandeln Sie doppelte Schreibvorgänge.
- Senden Sie dem Support App-Zweck, Berechtigungen, Testschritte, HTTPS-Adressen und Kontaktdaten. Senden Sie keine geheimen Schlüssel oder Benutzertokens.
Diese Referenz wurde am 27. September 2026 mit der aktuellen v1-Implementierung abgeglichen. Prüfen Sie vor der Nutzung neuer Endpunkte oder Felder die aktuelle OpenAPI-Datei. Akzeptieren Sie unbekannte Antwortfelder und protokollieren Sie unbekannte Ereignistypen, ohne die App zu stoppen.
Endpunktreferenz
Öffnen Sie eine Operation, um Parameter, Anfrageinhalt, Antwortfelder und HTTP-Statuswerte zu sehen. Schemabeschreibungen sind wie in OpenAPI auf Englisch. Beispielantworten zeigen die Struktur, keine echten Restaurantdaten.
17 / 17 Operationen
GET/healthDienststatus
Berechtigung: Öffentlich
Erfolgreiche Antwort · 200
{
"status": "ok",
"service": "api-v1",
"ts": "2026-09-27T00:00:00.000Z"
}Alle Antwortfelder und Typen
{
"type": "object",
"properties": {
"status": {
"type": "string",
"enum": [
"ok"
]
},
"service": {
"type": "string",
"enum": [
"api-v1"
]
},
"ts": {
"type": "string",
"format": "date-time"
}
},
"required": [
"status",
"service",
"ts"
]
}200 Success
POST/oauth/tokenCodeaustausch / Token-Erneuerung
Berechtigung: Öffentlich
JSON only in these examples. No client_credentials grant. PKCE S256 is mandatory, even for confidential clients. Serialize refresh operations per installation; old refresh-token reuse revokes the refresh family.
Anfrageinhalt · application/json
{
"type": "object",
"properties": {
"grant_type": {
"type": "string",
"enum": [
"authorization_code",
"refresh_token"
]
},
"client_id": {
"type": "string",
"maxLength": 200
},
"client_secret": {
"type": "string",
"description": "Required if a client secret has been configured.",
"maxLength": 200
},
"code": {
"type": "string",
"description": "Required for authorization_code; single use, valid for 60 seconds.",
"maxLength": 200
},
"redirect_uri": {
"type": "string",
"description": "Required for authorization_code. Exact registered URL.",
"maxLength": 500,
"format": "uri"
},
"code_verifier": {
"type": "string",
"description": "Required for authorization_code. Original PKCE verifier.",
"minLength": 43,
"maxLength": 128
},
"refresh_token": {
"type": "string",
"description": "Required for refresh_token grant.",
"maxLength": 200
}
},
"required": [
"grant_type",
"client_id"
]
}Erfolgreiche Antwort · 200
{
"access_token": "example",
"refresh_token": "example",
"token_type": "Bearer",
"expires_in": 3600,
"scope": "example"
}Alle Antwortfelder und Typen
{
"type": "object",
"properties": {
"access_token": {
"type": "string"
},
"refresh_token": {
"type": "string"
},
"token_type": {
"type": "string",
"enum": [
"Bearer"
]
},
"expires_in": {
"type": "integer",
"example": 3600
},
"scope": {
"type": "string",
"description": "Space-separated granted permissions."
}
},
"required": [
"access_token",
"refresh_token",
"token_type",
"expires_in",
"scope"
]
}200 Success400 invalid_request / invalid_grant401 invalid_client403 access_denied: app application requires DigiMenu approval, including TEST mode429 30 requests per minute per IP
GET/meInstallationsinformationen
Berechtigung: Gültiges App-Token
Erfolgreiche Antwort · 200
{
"installationId": "example",
"appId": "example",
"restaurantId": "example",
"scopes": [
"menu:read"
],
"mode": "TEST",
"restaurant": {
"slug": "example",
"name": "example"
}
}Alle Antwortfelder und Typen
{
"type": "object",
"properties": {
"installationId": {
"type": "string"
},
"appId": {
"type": "string"
},
"restaurantId": {
"type": "string"
},
"scopes": {
"type": "array",
"items": {
"type": "string",
"enum": [
"menu:read",
"orders:read",
"orders:write",
"tables:read",
"deliveries:read",
"deliveries:write",
"customers:read",
"webhooks:manage"
]
}
},
"mode": {
"type": "string",
"enum": [
"TEST",
"LIVE"
]
},
"restaurant": {
"type": "object",
"properties": {
"slug": {
"type": "string"
},
"name": {
"type": "string"
}
},
"required": [
"slug",
"name"
],
"nullable": true
}
},
"required": [
"installationId",
"appId",
"restaurantId",
"scopes",
"mode",
"restaurant"
]
}200 Success400 Invalid request or resource state401 Missing/expired token, revoked installation, or app removed403 APP_APPROVAL_REQUIRED (including TEST), missing scope, inactive subscription or delivery restriction404 Resource not found within the installation429 Rate or hourly quota exceeded
GET/menuMenü lesen
Berechtigung: menu:read
No pagination. Does not expose recipe cost, inventory, variants or internal preparation data.
Erfolgreiche Antwort · 200
{
"restaurant": {
"slug": "example",
"name": "example"
},
"categories": [
{
"id": "example",
"name": "example",
"order": 0,
"products": [
{
"id": "example",
"name": "example",
"description": null,
"price": 0,
"isActive": true,
"order": 0,
"allergens": null
}
]
}
]
}Alle Antwortfelder und Typen
{
"type": "object",
"properties": {
"restaurant": {
"type": "object",
"properties": {
"slug": {
"type": "string"
},
"name": {
"type": "string"
}
},
"required": [
"slug",
"name"
]
},
"categories": {
"type": "array",
"items": {
"type": "object",
"properties": {
"id": {
"type": "string"
},
"name": {
"type": "string"
},
"order": {
"type": "integer"
},
"products": {
"type": "array",
"items": {
"type": "object",
"properties": {
"id": {
"type": "string"
},
"name": {
"type": "string"
},
"description": {
"type": "string",
"nullable": true
},
"price": {
"type": "number",
"description": "Major currency units, not minor units. Currency is not included in this response."
},
"isActive": {
"type": "boolean"
},
"order": {
"type": "integer"
},
"allergens": {
"type": "string",
"nullable": true
}
},
"required": [
"id",
"name",
"description",
"price",
"isActive",
"order",
"allergens"
]
}
}
},
"required": [
"id",
"name",
"order",
"products"
]
}
}
},
"required": [
"restaurant",
"categories"
]
}200 Success400 Invalid request or resource state401 Missing/expired token, revoked installation, or app removed403 APP_APPROVAL_REQUIRED (including TEST), missing scope, inactive subscription or delivery restriction404 Resource not found within the installation429 Rate or hourly quota exceeded
GET/tablesTische auflisten
Berechtigung: tables:read
Returns a bare array. Does not expose QR secrets, sessions or occupancy.
Erfolgreiche Antwort · 200
[
{
"id": "example",
"name": "example",
"tableNumber": 0
}
]Alle Antwortfelder und Typen
{
"type": "array",
"items": {
"type": "object",
"properties": {
"id": {
"type": "string"
},
"name": {
"type": "string"
},
"tableNumber": {
"type": "integer"
}
},
"required": [
"id",
"name",
"tableNumber"
]
}
}200 Success400 Invalid request or resource state401 Missing/expired token, revoked installation, or app removed403 APP_APPROVAL_REQUIRED (including TEST), missing scope, inactive subscription or delivery restriction404 Resource not found within the installation429 Rate or hourly quota exceeded
GET/ordersBestellungen auflisten
Berechtigung: orders:read
| Parameter | Position / Typ | Beschreibung |
|---|---|---|
limit | query / integer | Page size. {"type":"integer","minimum":1,"maximum":100,"default":25} |
cursor | query / string | Pass the previous nextCursor unchanged. Stop when null. {"type":"string","maxLength":64} |
channel | query / string | Order channel. {"type":"string","enum":["TABLE","TAKEAWAY","DELIVERY"]} |
status | query / string | Use a known order status, e.g. OUT_FOR_DELIVERY; omit for all statuses. {"type":"string","maxLength":40} |
Erfolgreiche Antwort · 200
{
"data": [
{
"id": "example",
"shortId": "example",
"channel": "TABLE",
"status": "example",
"createdAt": "2026-09-27T00:00:00.000Z",
"scheduledFor": null,
"deliveryAddress": {},
"deliveryFeeMinor": null,
"items": [
{
"name": "example",
"quantity": 0,
"note": null
}
],
"courierRef": null
}
],
"nextCursor": null
}Alle Antwortfelder und Typen
{
"type": "object",
"properties": {
"data": {
"type": "array",
"items": {
"type": "object",
"properties": {
"id": {
"type": "string"
},
"shortId": {
"type": "string"
},
"channel": {
"type": "string",
"enum": [
"TABLE",
"TAKEAWAY",
"DELIVERY"
]
},
"status": {
"type": "string"
},
"createdAt": {
"type": "string",
"format": "date-time"
},
"scheduledFor": {
"type": "string",
"format": "date-time",
"nullable": true
},
"deliveryAddress": {
"type": "object",
"nullable": true,
"additionalProperties": true,
"description": "Coarse locality with contactRedacted=true by default. Exact address/contact is returned only with customers:read AND assignment to this app’s courier."
},
"deliveryFeeMinor": {
"type": "integer",
"description": "Minor currency units; currency not included.",
"nullable": true
},
"items": {
"type": "array",
"items": {
"type": "object",
"properties": {
"name": {
"type": "string"
},
"quantity": {
"type": "integer"
},
"note": {
"type": "string",
"nullable": true
}
},
"required": [
"name",
"quantity",
"note"
]
}
},
"courierRef": {
"type": "string",
"nullable": true
}
},
"required": [
"id",
"shortId",
"channel",
"status",
"createdAt",
"scheduledFor",
"deliveryAddress",
"deliveryFeeMinor",
"items",
"courierRef"
]
}
},
"nextCursor": {
"type": "string",
"nullable": true
}
},
"required": [
"data",
"nextCursor"
]
}200 Success400 Invalid request or resource state401 Missing/expired token, revoked installation, or app removed403 APP_APPROVAL_REQUIRED (including TEST), missing scope, inactive subscription or delivery restriction404 Resource not found within the installation429 Rate or hourly quota exceeded
GET/orders/{id}Bestelldetails
Berechtigung: orders:read
| Parameter | Position / Typ | Beschreibung |
|---|---|---|
id * | path / string | Resource identifier. {"type":"string"} |
Erfolgreiche Antwort · 200
{
"id": "example",
"shortId": "example",
"channel": "TABLE",
"status": "example",
"createdAt": "2026-09-27T00:00:00.000Z",
"scheduledFor": null,
"deliveryAddress": {},
"deliveryFeeMinor": null,
"items": [
{
"name": "example",
"quantity": 0,
"note": null
}
],
"courierRef": null
}Alle Antwortfelder und Typen
{
"type": "object",
"properties": {
"id": {
"type": "string"
},
"shortId": {
"type": "string"
},
"channel": {
"type": "string",
"enum": [
"TABLE",
"TAKEAWAY",
"DELIVERY"
]
},
"status": {
"type": "string"
},
"createdAt": {
"type": "string",
"format": "date-time"
},
"scheduledFor": {
"type": "string",
"format": "date-time",
"nullable": true
},
"deliveryAddress": {
"type": "object",
"nullable": true,
"additionalProperties": true,
"description": "Coarse locality with contactRedacted=true by default. Exact address/contact is returned only with customers:read AND assignment to this app’s courier."
},
"deliveryFeeMinor": {
"type": "integer",
"description": "Minor currency units; currency not included.",
"nullable": true
},
"items": {
"type": "array",
"items": {
"type": "object",
"properties": {
"name": {
"type": "string"
},
"quantity": {
"type": "integer"
},
"note": {
"type": "string",
"nullable": true
}
},
"required": [
"name",
"quantity",
"note"
]
}
},
"courierRef": {
"type": "string",
"nullable": true
}
},
"required": [
"id",
"shortId",
"channel",
"status",
"createdAt",
"scheduledFor",
"deliveryAddress",
"deliveryFeeMinor",
"items",
"courierRef"
]
}200 Success400 Invalid request or resource state401 Missing/expired token, revoked installation, or app removed403 APP_APPROVAL_REQUIRED (including TEST), missing scope, inactive subscription or delivery restriction404 Resource not found within the installation429 Rate or hourly quota exceeded
POST/orders/{id}/transitionsLieferstatus ändern
Berechtigung: orders:write
ASSIGNED_TO_COURIER → OUT_FOR_DELIVERY → DELIVERED or DELIVERY_FAILED. Same status is a no-op. This generic route does not run courier GPS/proof checks: courier apps should use /deliveries. No payment, kitchen, cancel, create-order or refund operation is exposed.
| Parameter | Position / Typ | Beschreibung |
|---|---|---|
id * | path / string | Resource identifier. {"type":"string"} |
Idempotency-Key | header / string | Optional. Unique per installation AND operation. A response is cached for 5 minutes in one process; not durable across restarts. Never reuse for another order or body. {"type":"string"} |
Anfrageinhalt · application/json
{
"type": "object",
"properties": {
"status": {
"type": "string",
"enum": [
"OUT_FOR_DELIVERY",
"DELIVERED",
"DELIVERY_FAILED"
]
}
},
"required": [
"status"
]
}Erfolgreiche Antwort · 201
{
"id": "example",
"shortId": "example",
"channel": "TABLE",
"status": "example",
"createdAt": "2026-09-27T00:00:00.000Z",
"scheduledFor": null,
"deliveryAddress": {},
"deliveryFeeMinor": null,
"items": [
{
"name": "example",
"quantity": 0,
"note": null
}
],
"courierRef": null
}Alle Antwortfelder und Typen
{
"type": "object",
"properties": {
"id": {
"type": "string"
},
"shortId": {
"type": "string"
},
"channel": {
"type": "string",
"enum": [
"TABLE",
"TAKEAWAY",
"DELIVERY"
]
},
"status": {
"type": "string"
},
"createdAt": {
"type": "string",
"format": "date-time"
},
"scheduledFor": {
"type": "string",
"format": "date-time",
"nullable": true
},
"deliveryAddress": {
"type": "object",
"nullable": true,
"additionalProperties": true,
"description": "Coarse locality with contactRedacted=true by default. Exact address/contact is returned only with customers:read AND assignment to this app’s courier."
},
"deliveryFeeMinor": {
"type": "integer",
"description": "Minor currency units; currency not included.",
"nullable": true
},
"items": {
"type": "array",
"items": {
"type": "object",
"properties": {
"name": {
"type": "string"
},
"quantity": {
"type": "integer"
},
"note": {
"type": "string",
"nullable": true
}
},
"required": [
"name",
"quantity",
"note"
]
}
},
"courierRef": {
"type": "string",
"nullable": true
}
},
"required": [
"id",
"shortId",
"channel",
"status",
"createdAt",
"scheduledFor",
"deliveryAddress",
"deliveryFeeMinor",
"items",
"courierRef"
]
}201 Success400 Invalid request or resource state401 Missing/expired token, revoked installation, or app removed403 APP_APPROVAL_REQUIRED (including TEST), missing scope, inactive subscription or delivery restriction404 Resource not found within the installation409 INVALID_TRANSITION429 Rate or hourly quota exceeded
GET/webhooksWebhook-Empfänger auflisten
Berechtigung: webhooks:manage
App-wide, not restaurant-specific. Register once per app, not once per installation.
Erfolgreiche Antwort · 200
[
{
"id": "example",
"url": "https://eklenti.example/webhooks",
"events": [
"order.created"
],
"active": true,
"disabledAt": null,
"createdAt": "2026-09-27T00:00:00.000Z"
}
]Alle Antwortfelder und Typen
{
"type": "array",
"items": {
"type": "object",
"properties": {
"id": {
"type": "string"
},
"url": {
"type": "string",
"format": "uri"
},
"events": {
"type": "array",
"items": {
"type": "string",
"enum": [
"order.created",
"order.paid",
"delivery.ready",
"delivery.assigned",
"delivery.picked_up",
"delivery.delivered",
"delivery.failed"
]
}
},
"active": {
"type": "boolean"
},
"disabledAt": {
"type": "string",
"format": "date-time",
"nullable": true
},
"createdAt": {
"type": "string",
"format": "date-time"
}
},
"required": [
"id",
"url",
"events",
"active",
"disabledAt",
"createdAt"
]
}
}200 Success400 Invalid request or resource state401 Missing/expired token, revoked installation, or app removed403 APP_APPROVAL_REQUIRED (including TEST), missing scope, inactive subscription or delivery restriction404 Resource not found within the installation429 Rate or hourly quota exceeded
POST/webhooksWebhook-Empfänger erstellen
Berechtigung: webhooks:manage
No idempotency-key support. Check GET /webhooks before retrying a create after a network timeout; a duplicate request creates another endpoint. Secret is returned once.
Anfrageinhalt · application/json
{
"type": "object",
"properties": {
"url": {
"type": "string",
"description": "Public HTTPS URL on port 443; credentials, redirects and private hosts are rejected.",
"format": "uri",
"maxLength": 2048
},
"events": {
"type": "array",
"items": {
"type": "string",
"enum": [
"order.created",
"order.paid",
"delivery.ready",
"delivery.assigned",
"delivery.picked_up",
"delivery.delivered",
"delivery.failed"
],
"maxLength": 64
},
"minItems": 1
}
},
"required": [
"url",
"events"
]
}Erfolgreiche Antwort · 201
{
"id": "example",
"url": "https://eklenti.example/webhooks",
"events": [
"order.created"
],
"active": true,
"disabledAt": null,
"createdAt": "2026-09-27T00:00:00.000Z",
"secret": "example"
}Alle Antwortfelder und Typen
{
"type": "object",
"properties": {
"id": {
"type": "string"
},
"url": {
"type": "string",
"format": "uri"
},
"events": {
"type": "array",
"items": {
"type": "string",
"enum": [
"order.created",
"order.paid",
"delivery.ready",
"delivery.assigned",
"delivery.picked_up",
"delivery.delivered",
"delivery.failed"
]
}
},
"active": {
"type": "boolean"
},
"disabledAt": {
"type": "string",
"format": "date-time",
"nullable": true
},
"createdAt": {
"type": "string",
"format": "date-time"
},
"secret": {
"type": "string",
"description": "Signing secret; returned once at creation."
}
},
"required": [
"id",
"url",
"events",
"active",
"disabledAt",
"createdAt",
"secret"
]
}201 Success400 Invalid request or resource state401 Missing/expired token, revoked installation, or app removed403 APP_APPROVAL_REQUIRED (including TEST), missing scope, inactive subscription or delivery restriction404 Resource not found within the installation429 Rate or hourly quota exceeded
DELETE/webhooks/{id}Webhook-Empfänger deaktivieren
Berechtigung: webhooks:manage
Soft disable; repeating for your already-disabled endpoint succeeds. No public re-enable endpoint: register a new endpoint and store its new secret.
| Parameter | Position / Typ | Beschreibung |
|---|---|---|
id * | path / string | Resource identifier. {"type":"string"} |
Erfolgreiche Antwort · 200
{
"id": "example",
"active": false
}Alle Antwortfelder und Typen
{
"type": "object",
"properties": {
"id": {
"type": "string"
},
"active": {
"type": "boolean",
"enum": [
false
]
}
},
"required": [
"id",
"active"
]
}200 Success400 Invalid request or resource state401 Missing/expired token, revoked installation, or app removed403 APP_APPROVAL_REQUIRED (including TEST), missing scope, inactive subscription or delivery restriction404 Resource not found within the installation429 Rate or hourly quota exceeded
GET/webhooks/{id}/deliveriesWebhook-Zustellprotokolle
Berechtigung: webhooks:manage
| Parameter | Position / Typ | Beschreibung |
|---|---|---|
id * | path / string | Resource identifier. {"type":"string"} |
limit | query / integer | Page size. {"type":"integer","minimum":1,"maximum":100,"default":25} |
cursor | query / string | Pass the previous nextCursor unchanged. Stop when null. {"type":"string","maxLength":64} |
Erfolgreiche Antwort · 200
{
"data": [
{
"id": "example",
"eventType": "example",
"status": "PENDING",
"attempts": 0,
"responseCode": null,
"lastError": null,
"createdAt": "2026-09-27T00:00:00.000Z"
}
],
"nextCursor": null
}Alle Antwortfelder und Typen
{
"type": "object",
"properties": {
"data": {
"type": "array",
"items": {
"type": "object",
"properties": {
"id": {
"type": "string"
},
"eventType": {
"type": "string"
},
"status": {
"type": "string",
"enum": [
"PENDING",
"DELIVERED",
"FAILED"
]
},
"attempts": {
"type": "integer"
},
"responseCode": {
"type": "integer",
"nullable": true
},
"lastError": {
"type": "string",
"nullable": true
},
"createdAt": {
"type": "string",
"format": "date-time"
}
},
"required": [
"id",
"eventType",
"status",
"attempts",
"responseCode",
"lastError",
"createdAt"
]
}
},
"nextCursor": {
"type": "string",
"nullable": true
}
},
"required": [
"data",
"nextCursor"
]
}200 Success400 Invalid request or resource state401 Missing/expired token, revoked installation, or app removed403 APP_APPROVAL_REQUIRED (including TEST), missing scope, inactive subscription or delivery restriction404 Resource not found within the installation429 Rate or hourly quota exceeded
GET/deliveries/{runId}Kurierfahrt
Berechtigung: deliveries:read
Requires a courier installation created by granting deliveries:write. Reads only that installation’s courier runs.
| Parameter | Position / Typ | Beschreibung |
|---|---|---|
runId * | path / string | Resource identifier. {"type":"string"} |
Erfolgreiche Antwort · 200
{
"id": "example",
"status": "example",
"startedAt": null,
"completedAt": null,
"routeDistanceMeters": null,
"routeDurationSeconds": null,
"orders": [
{
"id": "example",
"status": "example"
}
],
"location": {
"lat": 0,
"lng": 0,
"at": "2026-09-27T00:00:00.000Z"
}
}Alle Antwortfelder und Typen
{
"type": "object",
"properties": {
"id": {
"type": "string"
},
"status": {
"type": "string"
},
"startedAt": {
"type": "string",
"format": "date-time",
"nullable": true
},
"completedAt": {
"type": "string",
"format": "date-time",
"nullable": true
},
"routeDistanceMeters": {
"type": "integer",
"nullable": true
},
"routeDurationSeconds": {
"type": "integer",
"nullable": true
},
"orders": {
"type": "array",
"items": {
"type": "object",
"properties": {
"id": {
"type": "string"
},
"status": {
"type": "string"
}
},
"required": [
"id",
"status"
]
}
},
"location": {
"type": "object",
"properties": {
"lat": {
"type": "number"
},
"lng": {
"type": "number"
},
"at": {
"type": "string",
"format": "date-time"
}
},
"required": [
"lat",
"lng",
"at"
],
"nullable": true
}
},
"required": [
"id",
"status",
"startedAt",
"completedAt",
"routeDistanceMeters",
"routeDurationSeconds",
"orders",
"location"
]
}200 Success400 Invalid request or resource state401 Missing/expired token, revoked installation, or app removed403 APP_APPROVAL_REQUIRED (including TEST), missing scope, inactive subscription or delivery restriction404 Resource not found within the installation429 Rate or hourly quota exceeded
POST/deliveries/{runId}/departFahrt beginnen
Berechtigung: deliveries:write
ASSIGNED → ON_ROUTE. Moves assigned orders to OUT_FOR_DELIVERY. A second depart fails with INVALID_RUN_STATE; reconcile GET before retrying.
| Parameter | Position / Typ | Beschreibung |
|---|---|---|
runId * | path / string | Resource identifier. {"type":"string"} |
Erfolgreiche Antwort · 201
{
"ok": true,
"orders": 0
}Alle Antwortfelder und Typen
{
"type": "object",
"properties": {
"ok": {
"type": "boolean"
},
"orders": {
"type": "integer"
}
},
"required": [
"ok",
"orders"
]
}201 Success400 Invalid request or resource state401 Missing/expired token, revoked installation, or app removed403 APP_APPROVAL_REQUIRED (including TEST), missing scope, inactive subscription or delivery restriction404 Resource not found within the installation429 Rate or hourly quota exceeded
POST/deliveries/{runId}/pingsStandort senden
Berechtigung: deliveries:write
The implementation resolves the active run from the authenticated courier and does not use runId to select a run. Send your assigned active run ID. Pings may be dropped with stored=false / THROTTLED; do not immediately retry.
| Parameter | Position / Typ | Beschreibung |
|---|---|---|
runId * | path / string | Resource identifier. {"type":"string"} |
Anfrageinhalt · application/json
{
"type": "object",
"properties": {
"lat": {
"type": "number",
"minimum": -90,
"maximum": 90
},
"lng": {
"type": "number",
"minimum": -180,
"maximum": 180
},
"accuracyM": {
"type": "number"
},
"headingDeg": {
"type": "number"
},
"speedMps": {
"type": "number"
},
"recordedAt": {
"type": "string",
"format": "date-time"
}
},
"required": [
"lat",
"lng"
]
}Erfolgreiche Antwort · 201
{
"stored": true,
"requeried": true
}Alle Antwortfelder und Typen
{
"oneOf": [
{
"type": "object",
"properties": {
"stored": {
"type": "boolean",
"enum": [
true
]
},
"requeried": {
"type": "boolean"
}
},
"required": [
"stored",
"requeried"
]
},
{
"type": "object",
"properties": {
"stored": {
"type": "boolean",
"enum": [
false
]
},
"reason": {
"type": "string",
"enum": [
"THROTTLED"
]
}
},
"required": [
"stored",
"reason"
]
}
]
}201 Success400 Invalid request or resource state401 Missing/expired token, revoked installation, or app removed403 APP_APPROVAL_REQUIRED (including TEST), missing scope, inactive subscription or delivery restriction404 Resource not found within the installation429 Rate or hourly quota exceeded
POST/deliveries/orders/{orderId}/deliveredZustellung bestätigen
Berechtigung: deliveries:write
Requires assigned order OUT_FOR_DELIVERY, fresh GPS near drop-off, and restaurant-selected proof. CODE requires code; PHOTO/SIGNATURE require a platform-issued upload unavailable through public v1. Do not treat these modes as a complete external-only integration.
| Parameter | Position / Typ | Beschreibung |
|---|---|---|
orderId * | path / string | Resource identifier. {"type":"string"} |
Anfrageinhalt · application/json
{
"type": "object",
"properties": {
"code": {
"type": "string",
"description": "Required when the restaurant uses CODE proof mode.",
"maxLength": 16
},
"proofRef": {
"type": "string",
"description": "PHOTO/SIGNATURE require a platform-issued upload. The public v1 API has no proof-upload endpoint; do not submit an arbitrary URL.",
"maxLength": 500
}
},
"required": []
}Erfolgreiche Antwort · 201
{
"ok": true
}Alle Antwortfelder und Typen
{
"type": "object",
"properties": {
"ok": {
"type": "boolean"
}
},
"required": [
"ok"
]
}201 Success400 Invalid request or resource state401 Missing/expired token, revoked installation, or app removed403 APP_APPROVAL_REQUIRED (including TEST), missing scope, inactive subscription or delivery restriction404 Resource not found within the installation429 Rate or hourly quota exceeded
POST/deliveries/orders/{orderId}/failedFehlgeschlagene Zustellung melden
Berechtigung: deliveries:write
Requires an assigned OUT_FOR_DELIVERY order. No general idempotency header support; reconcile order state after an ambiguous response.
| Parameter | Position / Typ | Beschreibung |
|---|---|---|
orderId * | path / string | Resource identifier. {"type":"string"} |
Anfrageinhalt · application/json
{
"type": "object",
"properties": {
"reason": {
"type": "string",
"enum": [
"CUSTOMER_ABSENT",
"WRONG_ADDRESS",
"CUSTOMER_REJECTED",
"OTHER"
]
},
"note": {
"type": "string",
"description": "Accepted by validation; currently not persisted by the delivery service.",
"maxLength": 200
}
},
"required": [
"reason"
]
}Erfolgreiche Antwort · 201
{
"ok": true
}Alle Antwortfelder und Typen
{
"type": "object",
"properties": {
"ok": {
"type": "boolean"
}
},
"required": [
"ok"
]
}201 Success400 Invalid request or resource state401 Missing/expired token, revoked installation, or app removed403 APP_APPROVAL_REQUIRED (including TEST), missing scope, inactive subscription or delivery restriction404 Resource not found within the installation429 Rate or hourly quota exceeded
Integrationssupport
Senden Sie App-ID, Endpunkt und Fehlercode. Senden Sie keine Tokens oder geheimen Schlüssel.
support@digimenu.us