Appearance
Contract authority: OpenAPI is the machine-readable public v1 contract. This page is generated from the Privilege Integrator Pack. If prose and OpenAPI disagree, stop and contact Privilege.
5. Redemption flow
Public states (API): reserved → confirmed | cancelled, and confirmed → reversed.
text
validate → reserved
├─ confirm → confirmed
└─ cancel → cancelled
confirmed → reverse → reversed| Operation | Commercial meaning |
|---|---|
| validate | Check credential + create a short reservation/hold at this outlet |
| confirm | Sale succeeded; complete Privilege redemption (public state confirmed) |
| cancel | Sale did not complete; release the hold before confirm |
| reverse | After confirm; void/refund path in Privilege terms — history is retained, not deleted |
Do not call reverse on a reservation that was never confirmed — use cancel.
6. Validate
| Item | Contract |
|---|---|
| Method / path | POST /redemptions/validate |
| Scope | redemptions:validate |
| Headers | Authorization (Basic), Idempotency-Key (required) |
| Body | token (opaque Privilege token), externalOutletId |
Success (200) means the token is usable now at the mapped outlet and a hold exists:
valid: trueredemptionId— use this UUID on confirm / cancel / reverseprivilegePromotionId— map to your local POS promotion (not auth, not pricing)state: "reserved"benefit: nullexpiresAt— when the reservation ends (ISO-8601). Honor this; do not confirm after expiry.
Reservation length is server-configured (commonly on the order of a few minutes) and is clamped so it never exceeds the underlying credential/intent expiry. Always trust expiresAt from the response.
Common failures: TOKEN_INVALID, TOKEN_EXPIRED, OUTLET_UNMAPPED, OUTLET_DISABLED, MEMBERSHIP_INELIGIBLE, DEAL_NOT_REDEEMABLE, OFFER_UNSUPPORTED, auth/scope/rate-limit errors.
curl (placeholders only)
bash
curl -sS -X POST "${BASE_URL}/redemptions/validate" \
-u "${CLIENT_ID}:${CLIENT_SECRET}" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: ${IDEMPOTENCY_KEY}" \
-H "X-Request-Id: pos-corr-001" \
-d '{
"token": "'"${PRIVILEGE_TOKEN}"'",
"externalOutletId": "'"${EXTERNAL_OUTLET_ID}"'"
}'Example placeholders:
text
BASE_URL=https://dev.privilege.hr/api/integrations/v1
CLIENT_ID=pic_EXAMPLE_NOT_REAL
CLIENT_SECRET=pcs_EXAMPLE_NOT_REAL_DO_NOT_USE
EXTERNAL_OUTLET_ID=pos-outlet-001
PRIVILEGE_TOKEN=opaque_token_example_not_real_xxxxxxxx
IDEMPOTENCY_KEY=validate-pos-outlet-001-20260914-0001Never log or echo the opaque token or clientSecret.
7. Confirm
Call after the POS has successfully applied its local promotion and completed its own sale.
| Item | Contract |
|---|---|
| Method / path | POST /redemptions/{redemptionId}/confirm |
| Scope | redemptions:confirm |
| Headers | Basic auth, Idempotency-Key (required) |
| Path | redemptionId from validate |
| Body | required savings + receipt reference |
Required body fields:
externalReceiptId(max 128) — opaque POS transaction referencediscountAmount— Privilege-attributable savings only (0= genuine zero; omit is invalid)currency— must beEUR
Optional:
externalOutletId— if present, must resolve to the reserved outletcompletedAt(ISO date-time)
Do not send grossAmount, finalAmount, or basket-total aliases — Privilege returns a validation error and does not persist them.
Canonical example:
text
POS applied local promo mapped from privilegePromotionId; Privilege savings = 4.50 EUR
→ externalReceiptId=<opaque>, discountAmount=4.50, currency=EUR- Send
discountAmount: 0→ reported genuine zero. - Omit
discountAmount→ rejected.
Success (200): state: "confirmed", privilegePromotionId, benefit: null, privilegeRedemptionId, savings, savingsSource: "reported", savingsAttribution: "privilege_only_contract".
Idempotent replay of the same key + same request returns the same success without creating a second commercial redemption.
curl
bash
curl -sS -X POST "${BASE_URL}/redemptions/${REDEMPTION_ID}/confirm" \
-u "${CLIENT_ID}:${CLIENT_SECRET}" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: ${IDEMPOTENCY_KEY}" \
-d '{
"externalOutletId": "'"${EXTERNAL_OUTLET_ID}"'",
"externalReceiptId": "POS-RCPT-0001",
"discountAmount": 15,
"currency": "EUR",
"completedAt": "2026-09-14T12:01:30.000Z"
}'8. Cancel
Use when checkout is abandoned or fails after validate and before confirm (including unknown local promo mapping — apply no benefit, then cancel).
| Item | Contract |
|---|---|
| Method / path | POST /redemptions/{redemptionId}/cancel |
| Scope | redemptions:cancel |
| Headers | Basic auth, Idempotency-Key (required) |
| Body | optional reason, optional externalOutletId |
Success (200): state: "cancelled".
Confirmed redemptions cannot be cancelled — use reverse.
curl
bash
curl -sS -X POST "${BASE_URL}/redemptions/${REDEMPTION_ID}/cancel" \
-u "${CLIENT_ID}:${CLIENT_SECRET}" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: ${IDEMPOTENCY_KEY}" \
-d '{
"reason": "operator_abort",
"externalOutletId": "'"${EXTERNAL_OUTLET_ID}"'"
}'9. Reverse
Use after a successful confirm when the commercial outcome must be reversed in Privilege (e.g. void / return), to the extent your POS supports that path.
| Item | Contract |
|---|---|
| Method / path | POST /redemptions/{redemptionId}/reverse |
| Scope | redemptions:reverse |
| Headers | Basic auth, Idempotency-Key (required) |
| Body | required: at least one of reason or reasonCategory (non-empty; max 256 combined in practice) |
Success (200): state: "reversed", optional privilegeRedemptionId. The commercial history row is retained (not deleted).
Commercial reverse time-window — UNRESOLVED
v1 does not publish or enforce a commercial reverse deadline. Integrators must not assume unlimited reversal forever. Final operational policy may be defined separately by Privilege.
Partial refunds — not supported by this API
reverse is a full commercial reversal of the Privilege redemption. It is not a partial-refund API. Privilege does not document an automatic “full reverse then re-redeem” workaround (that path can affect tokens, eligibility limits, and accounting). If your POS needs partial-refund reconciliation with Privilege, escalate during onboarding — a pilot that requires partial Privilege refunds is not ready until a supported resolution exists.
curl
bash
curl -sS -X POST "${BASE_URL}/redemptions/${REDEMPTION_ID}/reverse" \
-u "${CLIENT_ID}:${CLIENT_SECRET}" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: ${IDEMPOTENCY_KEY}" \
-d '{
"reasonCategory": "storno",
"reason": "customer_return"
}'