Skip to content

Error Handling & Edge Cases ​

Robust error handling is essential for a reliable integration. This chapter covers the HTTP status codes you will encounter, the error response formats, and guidance for handling edge cases.

HTTP Status Code Reference ​

StatusMeaningTypical Causes
200SuccessRequest processed successfully
400Bad RequestInvalid state name, malformed payload, missing required fields
401UnauthorizedMissing, invalid, or expired API key
404Not FoundResource (order, command) does not exist
409ConflictOptimistic locking conflict (concurrent modification)
429Too Many RequestsRate limit exceeded (not currently implemented, but plan for it)
500Internal Server ErrorUnexpected server error
503Service UnavailableTemporary server unavailability

Error Response Shapes ​

The API uses two different error response formats.

ProblemDetail (RFC 7807) ​

Most errors from the GlobalExceptionHandler use the standard ProblemDetail format:

json
{
  "type": "about:blank",
  "title": "Bad Request",
  "status": 400,
  "detail": "Invalid order state name",
  "instance": "/orders/12345/statusupdate"
}

The type field is typically "about:blank". The title is a short classification. The detail contains a human-readable explanation. The instance is the request URI that caused the error.

Common ProblemDetail responses:

ScenarioStatusTitleDetail
Invalid state name400Illegal argumentNo enum constant ...
Missing required field400Illegal argumentLocation name is required.
Invalid order ID (order does not exist)400Illegal argumentOrder does not exist
Validation error400Validation error(validation message)
Database constraint violation400Constraint violationGeneric constraint message
License error401License error(license-related message)
Database operation error400Something went wrong during database operation.(error details)
Internal server error500Internal server error{uuid}: {message}

Each 500 response includes a UUID for tracing. Log this UUID when troubleshooting.

Legacy 401 Responses ​

Authentication errors return two possible shapes:

From AuthorizationInterceptor (missing/invalid key):

http
HTTP/1.1 401 Unauthorized
Content-Type: text/plain

{"error" : "Session not valid."}

Note: This response has Content-Type: text/plain, not application/json, even though the body is JSON. The response includes a random delay of 500-1499ms.

From UnAuthorizedException handler (expired session):

json
{
  "message": "Unauthorized",
  "uid": null
}

Both indicate the same thing: the API key is not valid. Re-authenticate or obtain a new key.

Edge Cases ​

SSE Reconnection with Offset Parameters ​

When the SSE connection drops:

  1. Detect the drop via onerror callback (EventSource) or connection timeout.
  2. Implement exponential backoff: 1s, 2s, 4s, 8s, capped at 60s.
  3. On reconnection, pass the last processed order number and command ID: /notifications/service/stream?orderNumber=12345&commandNumber=42
  4. Poll after reconnecting. Check an existing key before replacing it; a stream closure alone is not evidence of an invalid credential.

Use the connection-check and reconnection procedure.

Network Partitioning ​

Do not wait for the server's configured emitter timeout to detect a broken connection. Monitor event arrival and enforce a local idle/read threshold. Missing keepalives do not guarantee an immediate transport error.

When the server is reachable, use bounded fallback polling and reconnect SSE with backoff. When it is unreachable, back off both operations. After recovery, poll pending work and reconcile durable processing records before resubmitting anything to the register.

Duplicate Detection ​

Use separate durable processing records for orders and commands, scoped to the installation and location. Their number fields refer to different identifier sequences. An in-memory set cannot protect against duplicates after a restart.

Coordinate workers before external submission. Record the external result and acknowledgement outcome separately. If the result is unknown, reconcile it with the register before retrying. Choose retention based on your recovery requirements; an arbitrary one-hour expiry is not a guarantee against duplicates.

Partial Data — Nullable Fields ​

Fields without a value are omitted from the response — the API does not serialize null. Your client must not assume that a field is always present:

  • tableNumber — May be absent for takeout orders. Treat as "no table assigned."
  • userNumber — May be absent if the order was created without a user session.
  • state — Always present. Commands always report PLACED.
  • rows — Only present on orders. Commands have no rows.
  • guestCount — Present on orders (currently always 1).
  • readTableState — Present (true) only on read-table-state commands.
  • paymentNumber — Present only on request-bill commands (always 1).
  • exportMainData — Present (true) only on export-data / fetch-remote-menu commands.

The DTO also defines togo, moveTableTo, receipt, paymentDiscount, and customer, but the current backend never populates them, so they never appear in responses.

Safe access pattern:

javascript
// Use optional chaining and nullish coalescing
const table = order.tableNumber ?? "unknown";
const state = order.state ?? "PLACED";
const rows = order.rows ?? [];
const paymentDiscounts = order.paymentDiscount ?? [];

Order State Machine ​

text
                          +---> COMPLETED
                          |
  SELECTING --> NEW --> PLACED ----> ABORTED
                          |
                          +---> FAILED ---> PLACED (retry)
  • SELECTING: Customer is adding items. Not visible to service clients via the location endpoint.
  • NEW: Order created by a client app. Not yet placed.
  • PLACED: Customer submitted the order. This is the state your service client sees when polling.
  • COMPLETED: Order fulfilled. Set by your service client via status update.
  • ABORTED: Order cancelled. Set by your client or when a session closes.
  • FAILED: Processing error. Below the configured failure threshold, reporting failure returns the order to PLACED; at the threshold, operator recovery is needed. See failure handling.

When you update an order's status, the server records timestamps:

  • orderTime — Set when the order transitions to PLACED
  • completeTime — Set when the order transitions to COMPLETED or ABORTED
  • failedTime — Set when the order transitions to FAILED

Concurrency — coordinate integration workers ​

Multiple polling clients can receive the same orders. Coordinate workers and persist processing identifiers before external submission. A second status update is not guaranteed to fail because a first client changed the state. Do not rely on this endpoint to reserve work or transparently retry conflicts. Reconcile unknown outcomes before repeating external processing.

Handling Timeouts ​

Distinguish connection establishment, ordinary request deadlines, and SSE idle/read monitoring. For example, use a 30-second deadline for an ordinary request and investigate a 60-second gap without stream events. These are client configuration suggestions, not server response-time guarantees.

After a timed-out write, reconcile the outcome before repeating it. See Security & Best Practices for retry guidance.

Curl: Simulating Error Scenarios ​

Invalid API Key ​

bash
curl -i -H "api_key: invalid-key" "https://{host}/orders/location"

Non-existent Order ​

bash
curl -i -X PUT \
  -H "api_key: abc123-def456-ghi789" \
  -H "Content-Type: application/json" \
  -d '{"newState": "COMPLETED"}' \
  "https://{host}/orders/99999/statusupdate"

Invalid JSON Payload ​

bash
curl -i -X PUT \
  -H "api_key: abc123-def456-ghi789" \
  -H "Content-Type: application/json" \
  -d 'not-json' \
  "https://{host}/orders/12345/statusupdate"

Next Steps ​