Skip to content

DEVELOPERS · API v1

Build an app for DigiMenu

Apply for API access and explain your use case. After DigiMenu approval, connect with the restaurant’s permission. This guide covers the application process, setup and technical details.

Verified: 27 September 202617 operations

How apps work

API access requires an application and DigiMenu approval for each app. Explain its purpose, why it needs the API and which data it will use. Installation, tokens and webhooks are blocked before approval, including TEST mode. A subscription or developer portal access does not replace approval.

Your app runs on your own server. A restaurant administrator grants access in DigiMenu; your app calls the API with that installation’s OAuth token and receives signed webhooks. You do not upload code or ZIP files to DigiMenu servers.

API base: https://api.digimenu.us/api/v1. TEST and LIVE apps use the same host. TEST only restricts installation to the developer organization; it is not a separate or simulated database. Use a dedicated test restaurant.

  • Read menus and tables, list orders and build delivery integrations.
  • This API has no menu/product writes, order creation, inventory/CRM management, payment collection or refund endpoint. First-party DigiMenu apps have capabilities that are not exposed to third parties.
  • The starter runs on Node.js 22.18+ without extra packages: OAuth connection, menu reads, token refresh and signed webhook receipt.

Independent developer account

Apply at developers.digimenu.us without a restaurant account or subscription. Verify your email and describe yourself or your company, the app purpose and required data. After developer approval you can create an app application. Each app is reviewed separately; account approval alone does not grant API access.

Create an isolated test environment from an approved app’s settings. It never uses customer tables and supports sample menu, table and order reads plus order delivery-status changes. It does not simulate OAuth, courier or webhook flows. The sandbox_ key lasts 24 hours and cannot be used at the live /api/v1 address. Reset restores fixtures and revokes the old key.

HTTP · sandbox
GET https://api.digimenu.us/api/admin/developer-sandbox/v1/menu
Authorization: Bearer SANDBOX_TOKEN

For production, switch your approved app to LIVE. A restaurant administrator must separately grant access through the OAuth flow below. An independent developer account does not provide restaurant dashboard access. Suspending the developer account blocks its existing apps and sandbox keys too.

App registration and testing

Open developers.digimenu.us with your approved independent account. Business-account developers can continue using panel.digimenu.us/gelistirici, which requires Developer & Platform enabled in their organization. The app settings below apply to both portals.

  • Create an API access application draft. Explain the purpose and data needs in 40–2000 characters. The resulting clientId does not grant access by itself.
  • Register an exact HTTPS redirect URL, e.g. https://app.example/callback. Wildcards, credentials and fragments are rejected. Trailing slashes must match.
  • For the starter choose menu:read, orders:read and webhooks:manage. Check the granted scopes in each token response.
  • Save the purpose, redirect URLs and scopes, then submit for review. After DigiMenu approval you may create an optional client_secret. Once configured it is required for code exchange and refresh, and is displayed only once.
  • After your application is approved, authorize a test restaurant in your own organization in TEST mode. Use your own HTTPS domain or tunnel for callback and webhook URLs; HTTP localhost redirects cannot be registered.
starter.env
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-inbox
Terminal
node --env-file=starter.env digimenu-starter.mjs
# HTTPS proxy/tunnel → http://127.0.0.1:8787
# Open https://eklenti.example/connect

The starter is for development: sessions and tokens live in memory and require reauthorization after restart; webhook events are written to local disk. Before production add user authentication, encrypted persistent token storage, organization/installation mapping, a durable work queue and a retention policy. Do not commit starter.env.

OAuth and PKCE

PKCE S256 is mandatory for all clients. Generate a new code_verifier and state for each connection attempt and bind them to the user’s server session. The verifier must be 43–128 characters. Validate state before exchanging the code. Authorization codes are single-use and valid for 60 seconds.

Node.js
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();

After consent the callback receives code and state; denial returns error=access_denied and state. Do not collect a dashboard login token. Obtain app tokens through the exchange below. The client_credentials grant is not supported.

HTTP
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"
}
200 · JSON
{
  "access_token": "ACCESS_TOKEN",
  "refresh_token": "REFRESH_TOKEN",
  "token_type": "Bearer",
  "expires_in": 3600,
  "scope": "menu:read orders:read webhooks:manage"
}

Refresh and uninstall

Access tokens last 1 hour; refresh tokens last 60 days. Use expires_in. Each refresh returns a new access/refresh pair; store both atomically. Serialize refreshes per installation. Reusing an old refresh token revokes the installation’s refresh-token family.

HTTP
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"
}

Include client_secret if configured. If a refresh response is lost, do not blindly retry the old token; reconnect through OAuth. Do not loop on invalid_grant.

Uninstalling an app in the dashboard closes API access. Stop syncing on APP_REVOKED or APP_NOT_INSTALLED and ask the restaurant to authorize again. SUBSCRIPTION_REQUIRED must be resolved through the restaurant’s billing panel, not token refresh.

Scopes and data access

Each access token belongs to one restaurant and installation. Store separate grants/tokens for each restaurant, keyed by installationId and restaurantId. Scopes do not imply one another: orders:write does not include orders:read.

  • menu:read — Active categories and products, without menu writes.
  • orders:read — Order list/detail; excludes line prices, payment records and the customer table.
  • orders:write — Delivery-leg status transitions only, without payment, kitchen or cancellation operations.
  • tables:read — Active table ID, name and number; no QR secrets or occupancy.
  • deliveries:read / deliveries:write — Read/operate assigned courier runs. Granting write provisions a courier for the installation.
  • customers:read — Does not expose a /customers endpoint. Detailed order addresses require this scope AND assignment to your app’s courier.
  • webhooks:manage — Manage app receivers and delivery logs. Receiving events also requires orders:read or deliveries:read.

Requests, pagination and retries

cURL
curl 'https://api.digimenu.us/api/v1/orders?channel=DELIVERY&limit=25' \
  -H "Authorization: Bearer $ACCESS_TOKEN" \
  -H 'Accept: application/json'

Order and webhook-delivery lists default to limit=25, range 1–100. Pass nextCursor as cursor for the next request; stop at null. /tables and /webhooks return bare arrays. /menu returns restaurant/categories, not a data envelope.

Dates use ISO 8601. Menu price is in major units (149.90); deliveryFeeMinor is in minor units (14990). These responses do not include currency; establish the restaurant’s currency separately. null can indicate unavailable or inapplicable data.

Idempotency-Key applies only to /orders/{id}/transitions: a per-installation, five-minute, in-process response cache. Use a different key for every order/operation/body. It is not durable across restarts and is not a guarantee for all POSTs. Reconcile state before retrying other writes after ambiguous network failures.

Webhook setup and verification

Receivers belong to the app, not a restaurant; do not register a new receiver for every installation. Use a public HTTPS URL on port 443. Private/local hosts and redirects are rejected. Verify the signature over the raw request bytes before parsing JSON.

HTTP
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"]
}

The 201 response exposes secret only once. Store it securely. Receiver creation is not idempotent: after a lost response check GET /webhooks before retrying. If the secret is lost, disable the old receiver and create a new one.

Headers
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)
JSON · example
{
  "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" }
}

Compare signatures in constant time and reject timestamps outside ±300 seconds. The signed body id must match X-Digimenu-Event-Id. Persist deduplication by that ID and match restaurantId/installationId against your own installation records.

Return 2xx within 10 seconds, after storing the event durably. Failed delivery has up to 6 attempts, with delays of 30, 120, 600, 1,800 and 7,200 seconds before retries; worker timing may delay them. A total of 20 FAILED delivery records auto-disables the receiver.

Ordering and exactly-once delivery are not guaranteed. Fetch current order state using GET /orders/{id}. Inspect GET /webhooks/{id}/deliveries for attempts. There is no public replay or re-enable endpoint.

Event catalog

  • order.created — Takeaway/delivery order created. data: orderId, channel. Requires orders:read; not guaranteed for every table-order path.
  • order.paid — Channel-order payment settled. data: orderId. Requires orders:read.
  • delivery.ready — Assignment to an external courier network. data: orderId, runId and delivery details. Requires deliveries:read. Verify your assigned run through the API; avoid logging sensitive delivery data.
  • delivery.picked_up / delivery.delivered / delivery.failed — data: orderId, runId, status. Requires deliveries:read.
  • delivery.assigned — A reserved name accepted during registration; no producer exists in the current release. Do not make your workflow depend on it.

Courier integration

Install with deliveries:read, deliveries:write and webhooks:manage; add orders:read for order detail and customers:read for assigned-order contact details. After the restaurant assigns a delivery, read the runId from delivery.ready.

HTTP · sequence
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" }

Delivery confirmation checks fresh location, proximity and the restaurant’s proof mode. CODE requires the customer’s code. PHOTO/SIGNATURE require a DigiMenu-issued upload, but public v1 has no proof-upload endpoint. These modes cannot be treated as complete v1-only workflows.

Courier apps should use /deliveries. Generic /orders/{id}/transitions does not perform GPS or delivery-proof checks. The pings route resolves the active run from the authenticated courier; runId cannot select another run.

Embedded screens

The developer portal exposes embeddedUrl and embeddedOrigin. The screen runs in a sandboxed iframe: do not rely on cookies, session access or the parent DOM. Tokens are never sent through iframe messages. Start with a standalone HTTPS app.

The embedded bridge is not yet a stable integration contract: the current iframe sandbox and origin-targeted messaging require end-to-end validation together. Do not assume restaurantId/locale/theme or auto-resize messages will arrive. Validate this flow with support before production.

Errors and limits

Limits are 30 requests per 10 seconds and 5,000 per UTC hour window per installation, plus a global 600/minute per-IP limit. The token endpoint is limited to 30/minute per IP. Back off with jitter after 429; do not assume Retry-After is always present.

API · 403
{ "code": "insufficient_scope", "requiredScope": "orders:read", "message": "..." }
OAuth · 400
{ "error": "invalid_grant", "error_description": "..." }
  • 400 — Check types, allowed statuses and required fields. Unknown body fields are rejected. Validation errors may also use statusCode/message/error.
  • 401 — Refresh an expired token on APP_UNAUTHENTICATED. APP_REVOKED / APP_NOT_INSTALLED require reauthorization. For OAuth invalid_client check client_id and configured client_secret.
  • 403 — insufficient_scope, SUBSCRIPTION_REQUIRED or courier/proof restriction. Repeated requests cannot fix missing permission.
  • 404 — Resource missing or outside the token’s restaurant/courier access.
  • 409 — INVALID_TRANSITION: read the current order state before choosing the next transition.
  • 429 — rate_limited / quota_exceeded or global throttling. Slow the queue; hourly quota requires the next UTC hour window.
  • 5xx or network failure — Retry reads a bounded number of times. Reconcile writes before retrying.

Review and release

Apps start as DRAFT and TEST. Redirect URLs, scopes and descriptions are editable while DRAFT or REJECTED. Register at least one redirect and scope, then submit to IN_REVIEW. Approval changes it to APPROVED; use Go live to enter LIVE mode. Contact support for later configuration changes to an approved app.

  • Test approval, denial, incorrect state and an expired authorization code.
  • Verify tokens and records remain isolated between two test restaurants.
  • Exercise refresh, a lost response and uninstalling from the dashboard.
  • Test tampered, expired, duplicate and out-of-order webhooks.
  • Implement bounded retries for 429/transient errors and duplicate handling for writes.
  • Provide support with app purpose, scopes, test steps, HTTPS URLs and contact details, without secrets or user tokens.

Reference checked against the current v1 implementation on 27 September 2026. Check the current OpenAPI before using new endpoints or fields. Tolerate unknown response fields and record unknown event types without crashing.

Endpoint reference

Expand an operation for parameters, request body, response fields and HTTP statuses. Schema descriptions use English, shared with OpenAPI. Response examples show structure, not real restaurant data.

17 / 17 operations

GET/healthService health

Scope: Public

Success response · 200

Response example
{
  "status": "ok",
  "service": "api-v1",
  "ts": "2026-09-27T00:00:00.000Z"
}
All response fields and types
JSON Schema
{
  "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/tokenExchange an authorization code or rotate tokens

Scope: Public

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.

Request body · application/json

JSON Schema
{
  "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"
  ]
}

Success response · 200

Response example
{
  "access_token": "example",
  "refresh_token": "example",
  "token_type": "Bearer",
  "expires_in": 3600,
  "scope": "example"
}
All response fields and types
JSON Schema
{
  "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/meRead the token’s installation context

Scope: Valid app token

Success response · 200

Response example
{
  "installationId": "example",
  "appId": "example",
  "restaurantId": "example",
  "scopes": [
    "menu:read"
  ],
  "mode": "TEST",
  "restaurant": {
    "slug": "example",
    "name": "example"
  }
}
All response fields and types
JSON Schema
{
  "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/menuRead active categories and products

Scope: menu:read

No pagination. Does not expose recipe cost, inventory, variants or internal preparation data.

Success response · 200

Response example
{
  "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
        }
      ]
    }
  ]
}
All response fields and types
JSON Schema
{
  "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/tablesRead active tables

Scope: tables:read

Returns a bare array. Does not expose QR secrets, sessions or occupancy.

Success response · 200

Response example
[
  {
    "id": "example",
    "name": "example",
    "tableNumber": 0
  }
]
All response fields and types
JSON Schema
{
  "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/ordersList orders, newest first

Scope: orders:read

ParameterLocation / typeDescription
limitquery / integerPage size.
{"type":"integer","minimum":1,"maximum":100,"default":25}
cursorquery / stringPass the previous nextCursor unchanged. Stop when null.
{"type":"string","maxLength":64}
channelquery / stringOrder channel.
{"type":"string","enum":["TABLE","TAKEAWAY","DELIVERY"]}
statusquery / stringUse a known order status, e.g. OUT_FOR_DELIVERY; omit for all statuses.
{"type":"string","maxLength":40}

Success response · 200

Response example
{
  "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
}
All response fields and types
JSON Schema
{
  "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}Read an order

Scope: orders:read

ParameterLocation / typeDescription
id *path / stringResource identifier.
{"type":"string"}

Success response · 200

Response example
{
  "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
}
All response fields and types
JSON Schema
{
  "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}/transitionsUpdate a delivery-leg order status

Scope: 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.

ParameterLocation / typeDescription
id *path / stringResource identifier.
{"type":"string"}
Idempotency-Keyheader / stringOptional. 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"}

Request body · application/json

JSON Schema
{
  "type": "object",
  "properties": {
    "status": {
      "type": "string",
      "enum": [
        "OUT_FOR_DELIVERY",
        "DELIVERED",
        "DELIVERY_FAILED"
      ]
    }
  },
  "required": [
    "status"
  ]
}

Success response · 201

Response example
{
  "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
}
All response fields and types
JSON Schema
{
  "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/webhooksList this app’s webhook endpoints

Scope: webhooks:manage

App-wide, not restaurant-specific. Register once per app, not once per installation.

Success response · 200

Response example
[
  {
    "id": "example",
    "url": "https://eklenti.example/webhooks",
    "events": [
      "order.created"
    ],
    "active": true,
    "disabledAt": null,
    "createdAt": "2026-09-27T00:00:00.000Z"
  }
]
All response fields and types
JSON Schema
{
  "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/webhooksRegister a webhook receiver

Scope: 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.

Request body · application/json

JSON Schema
{
  "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"
  ]
}

Success response · 201

Response example
{
  "id": "example",
  "url": "https://eklenti.example/webhooks",
  "events": [
    "order.created"
  ],
  "active": true,
  "disabledAt": null,
  "createdAt": "2026-09-27T00:00:00.000Z",
  "secret": "example"
}
All response fields and types
JSON Schema
{
  "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}Disable a webhook endpoint

Scope: 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.

ParameterLocation / typeDescription
id *path / stringResource identifier.
{"type":"string"}

Success response · 200

Response example
{
  "id": "example",
  "active": false
}
All response fields and types
JSON Schema
{
  "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}/deliveriesRead delivery attempts for this app’s endpoint

Scope: webhooks:manage

ParameterLocation / typeDescription
id *path / stringResource identifier.
{"type":"string"}
limitquery / integerPage size.
{"type":"integer","minimum":1,"maximum":100,"default":25}
cursorquery / stringPass the previous nextCursor unchanged. Stop when null.
{"type":"string","maxLength":64}

Success response · 200

Response example
{
  "data": [
    {
      "id": "example",
      "eventType": "example",
      "status": "PENDING",
      "attempts": 0,
      "responseCode": null,
      "lastError": null,
      "createdAt": "2026-09-27T00:00:00.000Z"
    }
  ],
  "nextCursor": null
}
All response fields and types
JSON Schema
{
  "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}Read a run assigned to this app

Scope: deliveries:read

Requires a courier installation created by granting deliveries:write. Reads only that installation’s courier runs.

ParameterLocation / typeDescription
runId *path / stringResource identifier.
{"type":"string"}

Success response · 200

Response example
{
  "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"
  }
}
All response fields and types
JSON Schema
{
  "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}/departStart an assigned run

Scope: deliveries:write

ASSIGNED → ON_ROUTE. Moves assigned orders to OUT_FOR_DELIVERY. A second depart fails with INVALID_RUN_STATE; reconcile GET before retrying.

ParameterLocation / typeDescription
runId *path / stringResource identifier.
{"type":"string"}

Success response · 201

Response example
{
  "ok": true,
  "orders": 0
}
All response fields and types
JSON Schema
{
  "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}/pingsSend a courier location

Scope: 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.

ParameterLocation / typeDescription
runId *path / stringResource identifier.
{"type":"string"}

Request body · application/json

JSON Schema
{
  "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"
  ]
}

Success response · 201

Response example
{
  "stored": true,
  "requeried": true
}
All response fields and types
JSON Schema
{
  "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}/deliveredConfirm delivery with location and proof

Scope: 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.

ParameterLocation / typeDescription
orderId *path / stringResource identifier.
{"type":"string"}

Request body · application/json

JSON Schema
{
  "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": []
}

Success response · 201

Response example
{
  "ok": true
}
All response fields and types
JSON Schema
{
  "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}/failedReport a failed delivery

Scope: deliveries:write

Requires an assigned OUT_FOR_DELIVERY order. No general idempotency header support; reconcile order state after an ambiguous response.

ParameterLocation / typeDescription
orderId *path / stringResource identifier.
{"type":"string"}

Request body · application/json

JSON Schema
{
  "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"
  ]
}

Success response · 201

Response example
{
  "ok": true
}
All response fields and types
JSON Schema
{
  "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

Integration support

Send your app ID, endpoint and error code. Do not send tokens or secrets.

support@digimenu.us