List shop purchases
List the purchases and earned rewards that are delivered by your API and have not yet been confirmed by your server.
GET /v1/shop/purchases · requires shop:fulfil
Use it to find pending deliveries your server has not confirmed, for example after your server was down. Deliver shop items from your own platform explains the whole flow.
Query parameters
Prop
Type
Returns
data is a list of purchases awaiting confirmation, by purchaseId, oldest first. The response also includes page, with nextCursor and limit.
Prop
Type
item contains id, name and itemKey. reward, on an earned reward, contains id, sourceKind, sourceId and sourceLabel.
player is set when the member bought the item in a game's shop (see The game loop). It contains game, the game's slug, playerId, the game's own ID for the player, and proof, basic or verified: how far the connection was proven when the item was bought. It is read from the purchase, not from the connection as it is now, so a player who moves to another ID later does not redirect the item. A purchase made in Discord, an earned reward, and a purchase whose player was erased with Delete a player read null. A key that is limited to some games receives the player only on a purchase made through one of its games; on the purchases of any other game player is null, and the rest of the purchase is unchanged. A key with no games listed sees every player.
Example
curl "https://ceejay.communitystudios.xyz/api/v1/shop/purchases" \
-H "Authorization: Bearer $CEEJAY_API_KEY"The TypeScript example uses ceejay.shop.listPurchases from the TypeScript SDK. It returns the full response below, with the list in data.
{
"success": true,
"data": [
{
"purchaseId": 4411,
"deliveryId": "shop_4411",
"event": "shop.purchase",
"state": "processing",
"item": {
"id": 12,
"name": "Gold pack",
"itemKey": "gold_500"
},
"discordId": "234567890123456789",
"pricePaid": 500,
"createdAt": "2026-10-07T18:00:00.000Z",
"pendingSince": "2026-10-07T18:00:02.000Z",
"completeBy": "2026-10-08T18:00:02.000Z",
"attempts": 1,
"lastAttemptAt": "2026-10-07T18:00:02.000Z",
"answer": null,
"input": null,
"player": {
"game": "rugby_manager",
"playerId": "p_19",
"proof": "verified"
}
},
{
"purchaseId": 4412,
"deliveryId": "shop_4412",
"event": "reward.earned",
"state": "retrying",
"item": {
"id": null,
"name": "Founder skin",
"itemKey": "skin_founder"
},
"discordId": "234567890123456789",
"pricePaid": 0,
"createdAt": "2026-10-07T18:20:00.000Z",
"pendingSince": null,
"completeBy": null,
"attempts": 2,
"lastAttemptAt": "2026-10-07T19:20:04.000Z",
"answer": null,
"input": null,
"player": null,
"reward": {
"id": 31,
"sourceKind": "level",
"sourceId": "10",
"sourceLabel": "Level 10"
}
}
],
"page": {
"nextCursor": null,
"limit": 50
}
}What waiting means
A purchase of a Delivered by your API item, or an earned reward that is set to be delivered by your API, is awaiting confirmation in one of two states:
state | What happened | What happens next |
|---|---|---|
processing | Your server responded to the delivery with 202, or with { "status": "pending" } | CEEJAY awaits Complete a shop delivery. The delivery is never sent again. After completeBy, 24 hours later, CEEJAY refunds the member |
retrying | The delivery failed: your server did not respond, or responded with 408, 429 or a 5xx | CEEJAY retries it about every hour, and refunds the member after 3 failed attempts in total. You can also finish it with Complete a shop delivery |
A purchase in processing is never sent again, so this list is how a server that lost a delivery learns of it. Finish each purchase with Complete a shop delivery, using its purchaseId.
attempts and lastAttemptAt depend on the state. While retrying they are the failed deliveries so far and the time of the last one. While processing they describe the delivery your server responded to with pending: attempts is 1 or more, and lastAttemptAt is the same time as pendingSince.
Paging and polling
Pass page.nextCursor as cursor to get the next page, and stop when it is null. A cursor is valid only for the request that returned it. Polling once a minute counts as one request per minute against your key's rate limit.
The list never returns the item's delivery URL, its signing secret or the rest of its settings, the text of the last delivery error, team notes, member names and avatars, or purchases of items that are not delivered by your API.
Errors
| Status | code | error | When |
|---|---|---|---|
400 | bad_request | status must be 'waiting' | status was sent and is not waiting |
400 | bad_request | limit must be a whole number of 0 or more | limit is negative, a fraction or text, or was sent more than once |
400 | bad_request | cursor is not one this request gave out | cursor is not a page.nextCursor value returned by this request |
404 | not_available | This is not available on this instance yet. | This feature is not available on this instance yet. Community details reports the same under features. The body includes feature |
Every error body also includes requestId, and a 400 with the code bad_request identifies the invalid field in field. Match on code; message text can change.
The errors every request shares (401, 403, 405, 429, 500) are on the Errors page.

