Errors
Error Response
Every error from Mekari POS uses the same JSON envelope:
{
"status": "error",
"code": "VALIDATION_FAILED",
"message": "validation failed",
"data": {
"fields": [
{ "field": "name", "code": "REQUIRED", "message": "this field is required" }
]
},
"meta": { "request_id": "<REQUEST_ID>" }
}
codeis stable and in UPPER_SNAKE case. Build your error handling oncode, not onmessage.messageis English text for people. It can change.data.fieldsappears on validation errors that name a field. A few business errors carry otherdatavalues (for exampledata.unlock_at); they are named where the error is explained.meta.request_ididentifies the request (usually the same value as theX-Request-Idresponse header). Include it, or theX-Request-Idheader, when you contact support.meta.company_cid(the merchant's Mekari POS company number) andmeta.user_id(the acting user) are added when the caller is known. A401has onlyrequest_id.
Errors from Mekari API Gateway
Mekari API Gateway checks every request before Mekari POS sees it. Its errors do not use the envelope above.
| HTTP | When |
|---|---|
401 | The signature is wrong, the Date header is more than 5 minutes off, or the endpoint is not in your application's scopes |
429 | Too many requests; see Rate Limits |
5xx | Mekari POS could not be reached (for example 520 or 522). Retry later with backoff. |
Error Codes
| HTTP | Code | Meaning |
|---|---|---|
400 | VALIDATION_FAILED | The body, a query parameter or a header value is invalid. See data.fields when present. |
400 | INVALID_CURSOR | The cursor is invalid, or was used with a different limit or different filters |
400 | USER_ID_REQUIRED | A write without X-User-ID, or with the header sent more than once |
400 | IDEMPOTENCY_KEY_REQUIRED | A POST without Idempotency-Key, or an empty or longer-than-128-character key |
401 | COMPANY_NOT_FOUND | The company of your application does not use Mekari POS |
403 | PLAN_FEATURE_REQUIRED | The merchant's plan lacks the Public API add-on, or the feature an endpoint needs. See Plan Feature Errors |
403 | SUBSCRIPTION_EXPIRED | The merchant's Mekari POS subscription has expired |
403 | OUTLET_FORBIDDEN | X-Outlet-ID is missing or not an active outlet of the merchant, or the acting user cannot access it |
403 | PERMISSION_DENIED | The acting user's role does not allow this action |
403 | APPROVAL_REQUIRED | A return, refund or exchange needs a manager approval and no approval_token was sent. See Approvals |
403 | APPROVAL_TOKEN_INVALID | The approval_token is unknown, used, expired, or issued for another order, user or type. data.reason tells which |
403 | APPROVER_NOT_ELIGIBLE | The approver is not allowed to approve at this outlet |
403 | PIN_INVALID | The manager PIN is wrong. data.attempts_remaining tells how many tries are left |
403 | OTP_REQUIRED | The approval needs a one-time code. Request a code first |
403 | OTP_NOT_ENABLED | The outlet does not use approval by one-time code |
403 | OTP_INVALID | The one-time code is wrong. data.attempts_remaining |
403 | OTP_EXPIRED | The one-time code expired, or its request_id is unknown |
403 | CUSTOMER_SEAT_LIMIT | The merchant's plan cannot hold more customers |
403 | FORBIDDEN | The request is not allowed; read message |
404 | NOT_FOUND | The resource does not exist, belongs to another merchant, or is not available at X-Outlet-ID |
409 | DUPLICATE_PHONE | Another customer already uses this phone number |
409 | DUPLICATE_CARD_NUMBER | Another customer already uses this card number |
409 | DUPLICATE_EXTERNAL_ORDER_ID | POST /orders with an external_order_id your order source already sent. data.order_id is the first order |
409 | DATA_CONFLICT | The change conflicts with existing data (for example a category name already used); read message |
409 | SHIFT_NOT_OPEN | The acting user has no open shift at the outlet (approval PIN and code calls, returns, refunds and exchanges, and orders created with X-User-ID) |
409 | PIN_LOCKED | Too many wrong PINs or codes. data.unlock_at tells when to try again |
409 | OTP_RESEND_COOLDOWN | A code was sent a moment ago. data.resend_after tells when to ask again |
409 | OTP_UNAVAILABLE | No code can be sent now. A PIN fallback is open until data.fallback_expires_at |
409 | INSUFFICIENT_PETTY_CASH | A cash refund is more than the petty cash of the user's shift |
409 | DINE_IN_ROUND_UNPAID | A return, refund or exchange of a Dine In round whose bill is not paid yet |
409 | SETTLEMENT_SALE_NOT_REVERSIBLE | A return, refund or exchange of the payment record of a whole Dine In table; reverse its rounds instead |
409 | KITCHEN_NOT_FULFILLED | A return, refund or exchange (or its quote) of an order of a kitchen sales type whose kitchen ticket is not served or completed yet, or that has no ticket; data.kitchen_status has its status (null without a ticket). Retry with the same Idempotency-Key once the kitchen is done |
409 | CART_REPRICED | expected_payment_fee of an exchange differs from the fee the server priced (quote again), or expected_total of an order differs from the total POS priced (data has the POS amounts). See Creating Orders |
409 | ORDER_SOURCE_NOT_REGISTERED | POST /orders before PUT /order-source. Register your order source first |
409 | SOURCE_CODE_TAKEN | Another application of the merchant already uses this source_code |
409 | SOURCE_CODE_LOCKED | The order source already has an order, so its source_code cannot change |
409 | SALES_TYPE_DISABLED | The outlet does not sell the sales_type of the order |
409 | IDEMPOTENCY_KEY_REUSED | The Idempotency-Key was already used for a different request |
409 | IDEMPOTENCY_IN_PROGRESS | A request with the same Idempotency-Key is still running; retry shortly |
422 | INVALID_USER | X-User-ID is not an active user of the merchant |
422 | UNPROCESSABLE | A business rule of the endpoint failed; read message |
422 | KITCHEN_ACCEPT_STOCK_INSUFFICIENT | An order with accepted_type auto was refused because stock is short. data.shortages names the items |
422 | POST_SALE_DISABLED | The merchant turned this operation (returns, refunds or exchanges) off in Pengaturan pesanan. See Returns, Refunds and Exchanges |
422 | POST_SALE_WINDOW_EXPIRED | The order is older than the day limit the merchant set for the operation. data.max_days is the limit in days |
422 | PARTNER_REFUND_ORIGINAL_TENDER | A partner order (created with POST /orders) is refunded to the original tender. Cash is refused above the cash it was paid |
422 | REFUND_TENDER_INVALID_AMOUNT | A refund tender amount is not above 0 |
422 | REFUND_TENDER_DUPLICATE | A payment method appears more than once in refund_tenders |
422 | REFUND_TENDER_NOT_ELIGIBLE | A refund tender is not a refundable tender of the order, and not the cash method. A Mekari Pay QRIS payment, points, deposit, customer_balance and a legacy tender are always refused |
422 | REFUND_MULTI_TENDER_REQUIRES_FULL | More than one refund tender, when the refund is not a full refund of an untouched order |
422 | REFUND_TENDER_SUM_MISMATCH | The refund_tenders amounts do not add up to the refund |
422 | REFUND_TENDER_EXCEEDS_REMAINING | A Dine In round's share of a table settlement tender cannot take that much more; refund the rest in cash |
500 | INTERNAL | Unexpected error. Retry with backoff; contact support with meta.request_id if it continues. |
Other codes can appear for business rules of a specific endpoint; the endpoint's description in the API reference names them, and message explains them.
A request to an unknown path, with an unsupported method, or with a body larger than 64 KB gets 404, 405 or 413 with an envelope that has no code.
Endpoints That Need a Plan Feature
On top of the Public API add-on, these endpoints need another feature in the merchant's plan. Without it they answer 403 PLAN_FEATURE_REQUIRED.
| Endpoints | Plan feature |
|---|---|
GET /promotions, GET /promotions/{id} | Product discounts |
GET /vouchers/{code} | Order discount vouchers |
/raw-materials, /recipes, stock reads and counts of raw materials (GET /inventory/stock/raw_material/{id}, PUT /inventory/snapshots/raw_material/{id}), and stock adjustments of raw materials | Raw materials and recipes |
GET /returns | Refund order list |
GET /refunds | Refund order list |
POST /returns/quote, POST /returns | Return orders |
POST /refunds | Refund order list |
POST /orders | Kitchen order management |
GET /exchanges | Exchange orders |
POST /exchanges/quote, POST /exchanges | Exchange orders |
POST /approvals/otp | Approval by one-time code |
GET /vouchers/{code} never answers "not found" for an unknown code: it answers 200 with "valid": false and "reason": "not_found".
Plan Feature Errors
PLAN_FEATURE_REQUIRED has two kinds of answer:
- A missing Public API add-on answers
403 PLAN_FEATURE_REQUIREDwith nodata. - A missing plan feature of an endpoint (the table above) answers
403 PLAN_FEATURE_REQUIREDwithdata.feature, the feature key, anddata.reason:
data.reason | Meaning |
|---|---|
not_in_plan | The merchant's plan does not include the feature. The merchant must change the plan. |
disabled | The plan includes the feature but it is switched off. The merchant can turn it on in Settings > Features. |
{
"status": "error",
"code": "PLAN_FEATURE_REQUIRED",
"message": "<MESSAGE>",
"data": { "reason": "disabled", "feature": "<FEATURE_KEY>" },
"meta": { "request_id": "<REQUEST_ID>" }
}
Build your handling on code and data.reason, not on message.
Returns, Refunds and Exchanges Switched Off
Two 422 codes come from the merchant's own settings for returns, refunds and exchanges (Pengaturan pesanan), not from the plan:
POST_SALE_DISABLED: the merchant has not turned this operation on. It is off for new merchants.POST_SALE_WINDOW_EXPIRED: the order is older than the day limit of the operation.data.max_daysis the limit in days.
See Returns, Refunds and Exchanges. Another request will not help: the merchant must change the setting.