Redeem a voucher from a POS
Your cashier scans or types a voucher code. Your POS checks it with XQUARE and, if it is good, redeems it. That takes two calls: validate, then redeem.
Scopes: vouchers:read for validate, vouchers:redeem for redeem.
1. Validate (optional, no side effects)
POST /v1/vouchers/validate
Authorization: Bearer xq_live_…
Idempotency-Key: pos-17-check-000482
Content-Type: application/json
{ "code": "XQ-7KD2-9PLM" }
Like every POST, validate needs an Idempotency-Key; use a new one for each check.
You can also send location_id (the location the cashier is at) and branch_id (see
Name the branch below).
The answer depends on the code:
- A voucher of your Space that can be redeemed now:
200with"valid": true, thevoucherand itstemplate. - A voucher of your Space that is refused:
200with"valid": false, anerrorobject (codeandmessage) and thevoucher. For example, the voucher has expired or is used up. - A code that does not exist, or belongs to another Space:
404 not_found.
{
"valid": false,
"error": { "code": "voucher_expired", "message": "Voucher has expired" },
"voucher": { "id": "…", "code": "XQ-7KD2-9PLM", "status": "active", "redemptions_used": 0, "redemptions_max": 1, "…": "…" }
}
Show your cashier a message for each code. Never parse the English message; it can change.
2. Redeem
POST /v1/vouchers/redeem
Authorization: Bearer xq_live_…
Idempotency-Key: pos-17-receipt-000482
Content-Type: application/json
{ "code": "XQ-7KD2-9PLM" }
A successful redemption answers 201. The body has the redemption (id, location_id,
redeemed_by) and the voucher with its new status and counts.
A refused redemption answers 422 with the reason as the error code, for example
voucher_expired or max_redemptions_reached.
Redeem runs the same checks as validate. You can skip step 1 if you do not need to show the voucher before charging.
Always send an Idempotency-Key. Use one key per redemption attempt, for example your
receipt number. If the network drops and you retry with the same key within 24 hours, you get
the original redemption back with Idempotent-Replayed: true. You do not get a second
redemption, and you do not get max_redemptions_reached either. The same key with a different
code or location returns 409 idempotency_key_reused. See
Retries and idempotency.
Name the branch
If your Space limits vouchers per branch, validate and redeem must say which branch the voucher
is used at. Send branch_id. A location_id that identifies exactly one of your branches also
works. The redemption counts against that branch's allocation.
- The branch must be one of your Space's branches. If it is not, you get
branch_not_in_space. - Without a branch you get
outlet_requiredorbranch_required.
Codes your POS should handle
code | Tell the cashier |
|---|---|
voucher_expired | Expired |
not_yet_valid | Not valid yet |
max_redemptions_reached | Already used |
voucher_void | Cancelled by the merchant |
invalid_day · blockout_date | Not valid today |
wrong_location | Not valid at this location |
booking_scoped_voucher | Must be used with its booking |
crossbrand_redeem_denied | Belongs to another brand |
staff_confirm_required | This perk must be confirmed by staff at the counter |
outlet_required · branch_required | Pick the branch |
outlet_not_allocated · outlet_quota_exhausted | Not available at this branch |
If the allocation cannot be checked right now, you get 503 temporarily_unavailable. Retry with
the same Idempotency-Key.
Full list: Errors.
Listing vouchers and templates
GET /v1/vouchers and GET /v1/voucher-templates list your Space's vouchers and voucher
templates, newest first. Page with limit and offset; the answer has data and has_more.
Getting told instead of asking
Subscribe to voucher.redeemed to keep your back office in step when a voucher is redeemed
anywhere: in the vendor panel, at another outlet, or by another POS. See
Webhook events.