Reversal
Undo a shop purchase, refund the player from the debit the order actually posted, and account for what was delivered.
A reversal undoes a purchase: the player gets their currency back, the awards the order granted are
revoked, and the quantity counts again toward the player's per-player limit. It needs its own scope,
shop:reverse, which neither catalog authoring nor fulfilment confers, and an Idempotency-Key.
Reverse an order
curl -X POST "$REWARD_API_URL/v1/shop/orders/ord_123/reversal" \
-H "Authorization: Bearer $TANITAR_API_KEY" \
-H "Idempotency-Key: $(uuidgen)" \
-H "Content-Type: application/json" \
-d '{
"reasonCode": "ITEM_OUT_OF_PRINT",
"reason": "The supplier discontinued this item; refunding every open order."
}'| Field | Required | Rules |
|---|---|---|
reasonCode | Yes | Your own stable code: uppercase letters, digits and _, at most 64 characters. The player sees it. |
reason | Yes | 1 to 1000 characters for your team. The player does not see it. |
acknowledgeCodeLoss | For code orders | Must be true to reverse an order that delivered a code. See Delivered codes. |
The body has no amount and no player. What is refunded is read from the ledger entries of the debit the order posted, never from the request and never from the order's advertised total, and it is credited to the player who placed the order. The refund is posted under a key derived from that debit, so a repeated reversal can never credit twice.
The response is the order in status reversed, with a reversal record:
| Field | Meaning |
|---|---|
initiator | operator for a reversal through this route, player for a player's own cancellation |
reasonCode, reason | What you sent (reason is only in your view) |
refund | What came back, per currency |
lines[] | Per line: listingKey, recovery, codesLost |
reversedAt | When |
actorId | The API key or administrator that reversed it; null for a player cancellation |
refundOperationKey | The ledger operation that credited the refund |
codeLossAcknowledged | Whether a code loss was acknowledged |
shop.order.reversed is published in the same transaction.
Which orders can be reversed
| Order status | Reversible |
|---|---|
settled, awaiting-fulfillment, in-progress, fulfilled | Yes |
rejected | No. Nothing was charged |
cancelled, reversed | No. The value already came back |
Reversing an order that is not reversible is refused with 409 SHOP_ORDER_NOT_REVERSIBLE, before
anything is written.
What happens to the goods
What each line's goods come to is fixed by its fulfilment kind; a reversal does not choose it:
| Fulfilment kind | recovery | Effect |
|---|---|---|
award | award-revoked | The award granted by the order is revoked |
manual | never-delivered | Nothing in Tanitar to take back; handle any physical return yourself |
code | code-loss-accepted | The code cannot be taken back; codesLost counts them |
Stock is not restored. A reversal does not put units back into counted stock or the code pool. If you want an item back on sale, top up its stock.
A free or fully discounted order has no debit. It can still be reversed; the
refund reads as a leg of "0" in the listing's currency.
Delivered codes
A code that has been delivered cannot be un-delivered: the player may already have redeemed it.
Reversing such an order is a decision to accept that loss, so Tanitar asks you to make it explicitly.
Without "acknowledgeCodeLoss": true, the reversal is refused with
422 SHOP_CODE_LOSS_UNACKNOWLEDGED before anything is written. If you need to revoke the code itself,
do it with the code's issuer.
Player cancellation
A player can cancel their own manual order with POST /player/v1/shop/orders/:orderId/cancel until
your team claims it. It performs a full refund, restores limit headroom and publishes
shop.order.reversed with initiator: "player", the reason code
SHOP_CANCELLED_BY_PLAYER, and status cancelled instead of reversed. Once you
claim the order, the player's cancellation is
refused with 409 SHOP_ORDER_TRANSITION_CONFLICT; if you want to refund it after that, reverse it.
What your players see
The player's view of a reversed or cancelled order shows initiator, reasonCode, refund, the
per-line recovery and reversedAt. It does not show your reason or who on your team reversed it,
so choose reason codes your storefront can translate into a message.
Refusals
| Status | errorCode | Cause |
|---|---|---|
404 | SHOP_ORDER_NOT_FOUND | No order with that id |
409 | SHOP_ORDER_NOT_REVERSIBLE | The order is rejected, cancelled or already reversed |
409 | SHOP_ORDER_TRANSITION_CONFLICT | The order changed state concurrently |
422 | SHOP_CODE_LOSS_UNACKNOWLEDGED | The order delivered a code and acknowledgeCodeLoss is not true |
422 | SHOP_ORDER_NOT_REFUNDABLE | The order names no currency to refund in |
422 | SHOP_REASON_REQUIRED | reasonCode or reason is missing, or reason is only whitespace. Nothing is read or written |
400 | None | reasonCode or reason is malformed: empty, too long, or reasonCode is not an uppercase code |
Orders and fulfilment
Read shop orders, fulfil manual orders, disclose a delivered code under audit, and understand what your players do on the player plane.
Membership tiers and rename pricing
Define your own membership tiers, grant and revoke them per player, and set the price a player pays to change their nickname.