A valid request URL is required to generate request examples{
"applied": [
{
"ruleId": "promo-threshold-tshirt",
"ruleType": "threshold",
"benefitCents": 398,
"discountLines": [
{
"skuId": "2f0a30932a471a86d885382399c476bc17fde6e613c42a73e2cf6a760fe4f774",
"quantity": 2,
"amountCents": -398
}
],
"claimedSkuIds": [
"2f0a30932a471a86d885382399c476bc17fde6e613c42a73e2cf6a760fe4f774"
]
}
],
"totalBenefitCents": 398
}{
"error": {
"code": "VALIDATION_FAILED",
"message": "<string>",
"statusCode": 123,
"fieldErrors": [
{
"field": "<string>",
"message": "<string>"
}
]
}
}{
"error": {
"code": "VALIDATION_FAILED",
"message": "<string>",
"statusCode": 123
}
}{
"error": {
"code": "VALIDATION_FAILED",
"message": "<string>",
"statusCode": 123
}
}{
"error": {
"code": "VALIDATION_FAILED",
"message": "<string>",
"statusCode": 123
}
}Evaluate the active promotion rules against a cart
DRY-RUN: loads every rule whose validity window contains the current instant, runs them against the cart snapshot you pass, and returns the best NON-CONFLICTING set of outcomes. Writes nothing and does not touch the cart — it answers ‘what would this basket earn automatically?’. Two rules that claim the same SKU conflict, and only the more valuable one survives, so the reply is a selection, not the sum of everything that matched. Use it to preview or verify an offer’s effect on a concrete basket, or to explain a till total; do NOT use it to grant a reduction — POST /v1/checkout/finalize re-computes promotions server-side and is the only thing that makes them real. Cart money is integer cents; applied[].benefitCents is a positive magnitude while discountLines[].amountCents is SIGNED (<= 0) so a breakdown sums straight into a total. Scope gotcha (tracked as solya-pos#943): despite being the till’s read path, this rides the back-office pos.promotion.manage scope, so a plain cashier token holding only pos.checkout.operate is refused 403 at the router gate. In practice pos.promotion.manage is the grant that works here.
A valid request URL is required to generate request examples{
"applied": [
{
"ruleId": "promo-threshold-tshirt",
"ruleType": "threshold",
"benefitCents": 398,
"discountLines": [
{
"skuId": "2f0a30932a471a86d885382399c476bc17fde6e613c42a73e2cf6a760fe4f774",
"quantity": 2,
"amountCents": -398
}
],
"claimedSkuIds": [
"2f0a30932a471a86d885382399c476bc17fde6e613c42a73e2cf6a760fe4f774"
]
}
],
"totalBenefitCents": 398
}{
"error": {
"code": "VALIDATION_FAILED",
"message": "<string>",
"statusCode": 123,
"fieldErrors": [
{
"field": "<string>",
"message": "<string>"
}
]
}
}{
"error": {
"code": "VALIDATION_FAILED",
"message": "<string>",
"statusCode": 123
}
}{
"error": {
"code": "VALIDATION_FAILED",
"message": "<string>",
"statusCode": 123
}
}{
"error": {
"code": "VALIDATION_FAILED",
"message": "<string>",
"statusCode": 123
}
}Authorizations
Authorization: Bearer <token>. Accepts EITHER a Keycloak access token (scopes-in-token) OR an opaque POS session token; both resolve to the same pos.* scope vocabulary the route guards enforce.
Body
Show child attributes
Show child attributes

