Errors
Error responses from the Pay-at-Table API use a nested error envelope. The only exception is an invalid token, which returns an OAuth-style body (see below).
Envelope
{
"error": {
"code": 400,
"message": "Validation failed",
"details": {
"amount": ["This value should be greater than 0."]
}
}
}
| Field | Notes |
|---|---|
error.code | The HTTP status code, repeated in the body. |
error.message | Human-readable explanation suitable for surfacing in your UI. |
error.details | Per-field validation errors on 400 (map of field name to an array of messages). null on every other error. |
Invalid token - 401
A request with an Authorization header whose token is invalid or revoked does not use the envelope above. It returns an OAuth-style body:
{
"error": "access_denied",
"error_description": "The access token provided is invalid."
}
A request with no Authorization header (or a malformed one) returns 403 with the standard envelope, not 401:
{
"error": {
"code": 403,
"message": "Access Denied. You do not have permission to perform this action.",
"details": null
}
}
Status codes
| HTTP | When |
|---|---|
400 | Request body fails validation (missing/invalid field). error.details maps each field to its problems; error.message is "Validation failed". |
401 | Authorization header present but the token is invalid or revoked. OAuth-style body (see above). |
403 | Missing/malformed Authorization header, token lacks partner access, or venue is not in the token's scope. |
404 | Bill endpoint: table unknown for the venue, or no open order on the table. Payment endpoint: order not found in venue. Payment status endpoint: order not found in venue, or no payment with that externalReference on the order. Features endpoint: venue has no active POS connection. The message states which case applies. |
409 | Payment endpoint, two distinct causes distinguishable by message: (a) same externalReference reused on the same order with a different payload - message lists the mismatching fields; (b) a new externalReference submitted while another payment on the same order is still pending - message: Another payment ("<externalReference>") on order "<orderId>" is awaiting POS confirmation. Poll it to a terminal state before submitting a new payment. Resolve (b) by polling the in-flight payment via the payment status endpoint until terminal, then resubmitting. |
422 | Order is closed or voided when a payment is submitted. |
502 | POS forwarding failed (payment endpoint only). See below. |
5xx | Unexpected server error. message is "Internal Server Error". |
502 POS forwarding errors
When the payment endpoint returns 502, Mozrest forwarded the payment to the underlying POS and the POS refused, errored, or was unreachable. The response body is:
{
"error": {
"code": 502,
"message": "The bill is currently open on the POS interface. Please ask the waiter to close any open ticket on this table and retry.",
"details": null
}
}
error.message is controlled by Mozrest (no raw POS body leaks) and is safe to surface to the guest or operator. error.details is currently always null on 502 - there is no machine-readable failure slug in the response today, so branch on the HTTP status and use message for display.
The cases behind a 502, and what to do:
| Failure case | Meaning | Recommended partner action |
|---|---|---|
| Check locked | The bill is currently open in the POS (a waiter or terminal has it on screen). The POS rejects payment writes while the check is "in use". | Surface message to the guest. Retry after the waiter releases the check. |
| Check already closed | The bill is already closed in the POS. No further payments can be applied. | Stop retrying. The bill is paid (or voided) on the POS side. |
| Check not found | The bill could not be found in the POS (deleted, moved, or not yet synced). | Re-fetch via /bill. If the order disappears, the table is no longer open. |
| Idempotency conflict | A different payment payload was already recorded against the POS with this externalReference. | Use a fresh externalReference. Do not retry the same one with a different body. |
| POS auth / configuration failure | Mozrest could not authenticate with the POS, Pay-at-Table is not configured for this venue's POS, or the order is missing its POS-side reference. | Do not retry - contact Mozrest support. |
| POS unavailable / unreachable | The POS returned a 5xx or Mozrest could not reach it (network / timeout). | Safe to retry shortly with the same externalReference. |
| POS rejected | The POS refused for a reason Mozrest does not yet map. | Surface message. Contact Mozrest support if it recurs. |
Per-POS adapters use server-side idempotency keyed on externalReference (e.g. Simphony's header.idempotencyId = md5(externalReference)), so retrying the same request after a 502 is safe - the POS deduplicates.
Whatever the cause, a 502 means the payment was not recorded in Mozrest and the order is not closed.
A 502 can only occur on immediate-confirmation venues (features.payAtTable.paymentConfirmation = "immediate"), where forwarding happens synchronously inside the POST. On deferred-confirmation venues, POS failures surface asynchronously - see the next section.
Asynchronous failures
On venues where features.payAtTable.paymentConfirmation = "deferred", the payment POST answers 202 Accepted with status: "pending" and the POS confirms afterwards. If the POS then rejects the payment - or never confirms within the 120-second ceiling - the failure is data, not an HTTP error: the payment flips to status: "failed" with a machine-readable failureCode, and the payment status endpoint keeps answering 200 OK with that state in the body. Do not expect a 4xx/5xx for a payment that failed after the 202.
failureCode slugs are stable and safe to branch on. Where applicable they use the same vocabulary as the 502 cases above:
failureCode | Meaning | Recommended partner action |
|---|---|---|
pos_confirmation_timeout | The POS did not confirm the payment within 120 seconds of submission. | Refund/void the charge on your side and inform the guest. Report recurring cases to Mozrest. |
pos_check_not_found | The check was closed or moved by staff while the payment was in flight. | Re-fetch /bill and restart the flow. |
pos_overpaid | The payment would exceed the outstanding balance - e.g. staff took a payment on the till concurrently. | Re-fetch /bill and submit a corrected amount with a fresh externalReference. |
pos_config_missing | Pay-at-Table is not correctly configured for this venue's POS. | Do not retry - contact Mozrest support. |
A failed payment never counts toward the bill's totals.paid and never closes the order. Full lifecycle, polling guidance, and per-state JSON examples: Payment status.