Skip to main content

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."]
}
}
}
FieldNotes
error.codeThe HTTP status code, repeated in the body.
error.messageHuman-readable explanation suitable for surfacing in your UI.
error.detailsPer-field validation errors on 400 (map of field name to an array of messages). On 502, an object whose code is a stable failure slug (see 502 POS forwarding errors). On the 409 pending-payment case, {"code": "pos_payment_pending"}. 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​

HTTPWhen
400Request body fails validation (missing/invalid field). error.details maps each field to its problems; error.message is "Validation failed".
401Authorization header present but the token is invalid or revoked. OAuth-style body (see above).
403Missing/malformed Authorization header, token lacks partner access, or venue is not in the token's scope.
404Bill 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.
409Payment 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.
422Order is closed or voided when a payment is submitted.
502POS forwarding failed (payment and bill endpoints; on the bill endpoint only for pos_rate_limited). See below.
5xxUnexpected 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 bill endpoint returns 502 only with pos_rate_limited, when the POS answers HTTP 429 on the bill read; every other bill-read failure keeps its existing status. 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": {
"code": "pos_check_locked"
}
}
}

error.message is controlled by Mozrest (no raw POS body leaks) and is safe to surface to the guest or operator. error.details.code carries a stable, machine-readable failure slug (table below) that you can branch on. Use message for display. When a POS failure has no specific mapping, the slug is pos_provider_rejected.

The cases behind a 502, and what to do:

error.details.codeMeaningRetry with the same externalReference?Recommended partner action
pos_check_lockedThe 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".YesSurface message to the guest. Retry after the waiter releases the check.
pos_check_already_closedThe bill is already closed in the POS. No further payments can be applied.No, terminalStop retrying. The bill is paid (or voided) on the POS side.
pos_check_not_foundThe bill could not be found in the POS (deleted, moved, or not yet synced).No, terminalRe-fetch via /bill. If the order disappears, the table is no longer open.
pos_idempotency_conflictA different payment payload was already recorded against the POS with this externalReference.No, terminalUse a fresh externalReference. Do not retry the same one with a different body.
pos_auth_failedMozrest could not authenticate with the POS.No, terminalDo not retry - contact Mozrest support.
pos_config_missingPay-at-Table is not configured for this venue's POS.No, terminalDo not retry - contact Mozrest support.
pos_order_reference_missingThe order is missing its POS-side reference (for example, the order is not yet placed on the POS).No, terminalDo not retry - contact Mozrest support.
pos_transport_errorMozrest could not reach the POS (network / timeout).YesRetry shortly with the same externalReference.
pos_provider_unavailableThe POS returned a 5xx.YesRetry shortly with the same externalReference.
pos_rate_limitedThe POS answered HTTP 429: it is rate limiting requests. message: The POS is rate limiting requests. Please retry shortly.YesRetry shortly with the same externalReference.
pos_provider_rejectedThe POS refused for a reason Mozrest does not yet map. Also the default slug for any unmapped forwarding failure.No, terminalSurface message. Contact Mozrest support if it recurs.
Retry safety

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 on the payment endpoint means the payment was not recorded in Mozrest and the order is not closed.

On the payment endpoint, 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:

failureCodeMeaningRecommended partner action
pos_confirmation_timeoutThe 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_foundThe check was closed or moved by staff while the payment was in flight.Re-fetch /bill and restart the flow.
pos_overpaidThe 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_missingPay-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.