Shipping
The shipping endpoints turn a player’s won physical items into a fulfillment
order. Use them when you drive everything from your backend
and render your own ship cart / orders surface. Pick shippable items from
inventory (shippable: true), then create an order.
LootBox Solutions does not charge for shipping over S2S. Creating an order records the request (with a computed
shippingCostMinorfor reference) and moves the items to ship-requested — it never calls your wallet. You own the wallet, so if you charge for shipping you do it on your side. That’s why every response’sbalanceAfterMinorisnull. (Cancelling an order the player placed from the game app, which did charge your wallet, credits that fee back: see Cancel an order.)
Create an order
POST https://{operator}.app.lootboxsolutions.com/api/s2s/shipping/orders{ "playerExternalId": "u_8431", "inventoryItemIds": [88, 91], "address": { "name": "Ada Lovelace", "line1": "12 Analytical Way", "line2": null, "city": "London", "region": "Greater London", "postalCode": "EC1A 1BB", "country": "GB", "phone": null }, "notes": "Leave with the concierge"}| Field | Required | Notes |
|---|---|---|
playerExternalId | ✅ | Must own every item. |
inventoryItemIds | ✅ | 1–50 inventory ids (from inventory). All must be unresolved and shippable, or the whole batch is rejected. Their values together must reach the operator’s shipping minimum (shipping.minShipmentValueMinor, 0 = none). |
address.* | ✅ | name, line1, city, region, postalCode, and a 2-letter country are required; line2 + phone are optional. |
contact.email | ✅ | Where order updates go. |
notes | — | Optional delivery note from the player (max 500 chars). Echoed back as userNotes. |
Response — 201
{ "order": { "publicId": "ord_3f2a…", "status": "pending", "currency": "EUR", "shippingCostMinor": 0, "realizedTotalCostMinor": null, "itemsValueTotalMinor": 320, "address": { "name": "Ada Lovelace", "line1": "12 Analytical Way", "city": "London", "region": "Greater London", "postalCode": "EC1A 1BB", "country": "GB" }, "userNotes": "Leave with the concierge", "createdAt": "2026-06-16T12:00:00Z", "carrier": null, "trackingNumber": null, "trackingUrl": null, "deliveredAt": null, "replacementsSentAt": null, "items": [ { "inventoryItemId": 88, "itemId": 906, "name": "Golden Sword", "imageUrl": "https://…/sword.png", "valueMinor": 320, "currency": "EUR", "waitingForStock": false } ] }, "balanceAfterMinor": null}| Field | Notes |
|---|---|
publicId | The order handle — pass it to the detail / cancel / address endpoints below. |
status | Order status (pending, …, shipped_with_replacements). |
shippingCostMinor, currency | The computed cost, in the operator currency, for your reference — LootBox Solutions does not debit it. One fee per order: the shipping cost of the order’s most expensive item (an item’s own, or your default one when it has none). |
realizedTotalCostMinor | Your actual recorded fulfillment cost for the order, in the operator currency, once you’ve entered it — otherwise null. Returned only on these S2S endpoints, never on the in-player shipping surface. |
itemsValueTotalMinor | The summed value of the order’s items, in the operator currency — the always-present fallback for cost metrics before realizedTotalCostMinor is recorded. |
userNotes | The player’s delivery note, or null. |
carrier, trackingNumber, trackingUrl | What your shipping desk entered for the parcel, each null until entered. trackingUrl is an optional http/https link to the carrier’s tracking page: offer the player a Track button only when it is set. |
deliveredAt | When the order was marked delivered, or null. |
replacementsSentAt | When the order was marked shipped_with_replacements, or null. |
items[] | One line per shipped item, valued in its own operator currency. Each carries an imageUrl (or null) for rendering the order card, and waitingForStock: true when the item was out of stock when shipping was requested, so the player can be told it is waiting for stock. |
balanceAfterMinor | Always null — shipping never touches the wallet. |
List a player’s orders
GET https://{operator}.app.lootboxsolutions.com/api/s2s/shipping/orders?playerExternalId=u_8431{ "orders": [ { "publicId": "ord_3f2a…", "status": "pending", "…": "…" } ] }Newest first. Each order has the same shape as the create response’s order.
One order’s detail
GET https://{operator}.app.lootboxsolutions.com/api/s2s/shipping/orders/{publicId}?playerExternalId=u_8431{ "order": { "publicId": "ord_3f2a…", "status": "pending", "…": "…" } }Scoped to the player — another player’s publicId returns ORDER_NOT_FOUND.
Cancel an order
Only a pending order can be cancelled. Cancelling returns every item to the
player’s inventory (back to unresolved, so they can re-ship or cash back) and
moves the order to cancelled.
POST https://{operator}.app.lootboxsolutions.com/api/s2s/shipping/orders/{publicId}/cancel{ "playerExternalId": "u_8431" }{ "order": { "publicId": "ord_3f2a…", "status": "cancelled", "…": "…" }, "balanceAfterMinor": null }Idempotent — cancelling an already-cancelled order returns 200 with the
same cancelled order and does nothing further. A cancel refunds the shipping
fee LootBox Solutions charged the player, and only that: an order created over S2S was
never charged, so nothing is refunded (balanceAfterMinor stays null) —
reverse your own shipping charge, if any, on your side. An order the player
placed from the game app was
charged from your wallet, and any cancel of it (yours over S2S, the player’s,
your desk’s or the platform’s) credits that fee back to your wallet once
(shipping:refund:<publicId> key; balanceAfterMinor stays null here).
Whoever cancels, your webhook receives a
fulfillment_notice
saying what was refunded.
Change the shipping address
Replace the ship-to address on a pending order — for example when the player fixes a typo before the order is processed. The address shape is the same as the create request.
POST https://{operator}.app.lootboxsolutions.com/api/s2s/shipping/orders/{publicId}/address{ "playerExternalId": "u_8431", "address": { "name": "Ada Lovelace", "line1": "5 Difference Engine Rd", "line2": null, "city": "London", "region": "Greater London", "postalCode": "EC1A 1BB", "country": "GB", "phone": null }}{ "order": { "publicId": "ord_3f2a…", "status": "pending", "address": { "…": "…" } } }The already-computed shippingCostMinor is not re-quoted — the order keeps
the cost it was created with. A later cancel refunds exactly what the player was
charged, which is nothing for an order created over S2S.
Past orders are unaffected by later edits: each order snapshots its own address
at creation.
Errors
| code | HTTP | when |
|---|---|---|
SHIPPING_DISABLED | 403 | shipping is turned off for your operator |
INVENTORY_NOT_SHIPPABLE | 422 | an item is missing, not owned by the player, not unresolved, or not a shippable type — the whole batch is rejected |
SHIPMENT_BELOW_MINIMUM | 422 | the items add up to less than the operator’s shipping minimum — nothing changes; error.minimumMinor, error.valueMinor and error.currency (the operator currency) say by how much, so you can tell the player to add items or wait |
ORDER_NOT_FOUND | 404 | no order with that publicId for this player |
SHIPMENT_NOT_CANCELLABLE | 422 | the order has moved past pending, so it can no longer be cancelled |
SHIPMENT_NOT_EDITABLE | 422 | the order has moved past pending, so its address can no longer be changed |
A bad signature returns 401; a malformed body returns 422 with the standard
{ "error": { "code", "message", "fields" } } envelope.
Same data, two callers. The game app drives the equivalent in-iframe ship cart with a session token. These S2S endpoints are the backend-driven equivalent — signed with your API key.