Complete a shop delivery
Tell CEEJAY that a shop item your server was delivering has been delivered, or that delivery failed.
POST /v1/shop/purchases/{id}/complete · requires shop:fulfil
Use it when a shop item set to Delivered by your API is awaiting confirmation from your server: your server responded to the delivery request with 202, or with { "status": "pending" }, and you now know the result. Deliver shop items from your own platform explains the whole flow, and List shop purchases lists the purchases that await your confirmation.
Path parameters
Prop
Type
Body parameters
Prop
Type
Returns
Prop
Type
These fields are inside data. Alongside success, the response also includes the original fields, which remain for as long as version 1 exists: purchaseId, status, alreadyDone (only when it is true) and refunded (only on the call that makes the refund).
Example
curl -X POST https://ceejay.communitystudios.xyz/api/v1/shop/purchases/4242/complete \
-H "Authorization: Bearer $CEEJAY_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"status": "delivered"
}'The TypeScript example uses ceejay.shop.completePurchase from the TypeScript SDK. It returns the full response below.
{
"success": true,
"purchaseId": 4242,
"status": "fulfilled",
"data": {
"purchaseId": 4242,
"status": "fulfilled",
"alreadyDone": false,
"refunded": null,
"duplicate": false
}
}{
"success": true,
"purchaseId": 4242,
"status": "refunded",
"refunded": 500,
"data": {
"purchaseId": 4242,
"status": "refunded",
"alreadyDone": false,
"refunded": 500,
"duplicate": false
}
}{
"success": true,
"purchaseId": 4242,
"status": "fulfilled",
"alreadyDone": true,
"data": {
"purchaseId": 4242,
"status": "fulfilled",
"alreadyDone": true,
"refunded": null,
"duplicate": false
}
}Which purchases it applies to
A purchase of a Delivered by your API item of your own community that is awaiting an outcome: shown as Being delivered in Commerce → Shop → Redemptions, or under Failed delivery because an earlier attempt could not reach your server. Sending the same request again is safe: it returns alreadyDone.
A purchase that is already delivered or refunded returns 409 with the code wrong_status. Once a purchase is delivered you cannot mark it failed, and once it is refunded you cannot mark it delivered.
An earned reward, such as one for reaching a level, that is set to be delivered by your API takes the same call. delivered completes it. failed marks it failed with your message: no payment was taken, so nothing is refunded.
What it does
deliveredmarks the purchase fulfilled. It moves to Done in Redemptions.failedrefunds the member the full price, puts a limited item back in stock and notifies your team in CEEJAY that the delivery failed on your side. Your message is the reason.- Shop & Economy pages that are open refresh automatically.
Retrying safely
Send an idempotencyKey with every call you might need to retry, such as after a timeout. It is a string of 1 to 128 characters that you choose, sent in the JSON body: an Idempotency-Key header is not read.
- Retrying with the same key returns the original response. When the same request is sent again with its key, CEEJAY returns the original response, with
duplicate: trueand the headerIdempotent-Replayed: true. - Each key is tied to one request payload. The same request means the same purchase, status and message. Two requests for one event need two keys, for example
match-9:coinsandmatch-9:xp. A key that was used for a different request, here or on any other request of the API, returns409with the codeidempotency_key_reused, and nothing is changed. - A rejected request does not use up the key. A call that was rejected can be retried with the same key once the cause is fixed.
- A key belongs to the API key that sent it. Another API key of your community can send the same text, and it is a different key.
- Keys are retained for 30 days. Use a value you can regenerate after a crash, such as an order ID or a match ID.
- On an instance that does not support idempotency keys yet, a call that sends one returns
503with the codeidempotency_unavailable, and nothing is changed. Community details reports whether keys are supported, underfeatures.retryKeys.idempotency_keyis not accepted on this endpoint; useidempotencyKey. - Without a key, a retried call is a new call.
Completing a purchase twice has the same effect as completing it once, so when a first request was started more than a minute ago and never finished, a retry with the same key is processed again and returns normally. After a 502 or a 504, retry the same request: it is safe to repeat, with or without a key.
Errors
| Status | code | error | When |
|---|---|---|---|
400 | bad_request | A numeric purchase id is required | The purchase ID in the path is not a whole number, or has a leading zero |
400 | bad_request | The purchase id cannot exceed 2147483647 | The purchase ID is above 2,147,483,647 |
400 | bad_request | status must be 'delivered' or 'failed' | status is missing or is neither delivered nor failed. A body that is not a JSON object returns this too |
400 | bad_request | message must be text | message was sent and is not text |
400 | bad_request | message must be 500 characters or fewer | message is longer than 500 characters |
400 | bad_request | message cannot contain a null character | message contains a null character (U+0000), which cannot be stored |
400 | bad_request | message must be valid Unicode text | message contains an unpaired surrogate, such as the JSON "\ud800" |
400 | bad_request | idempotencyKey must be a string of 1-128 characters | The key is empty, longer than 128 characters or not text, or contains an unpaired surrogate, such as the JSON "\ud800" |
400 | bad_request | idempotencyKey cannot contain a null character | The key contains a null character (U+0000) |
400 | bad_request | The request was not valid | CEEJAY's bot, which finishes the purchase, rejected the request as invalid |
404 | purchase_not_found | Purchase not found | No purchase with this ID on your server whose item is delivered by your API |
404 | purchase_not_found | This purchase no longer exists on this server | The purchase was removed while it was being updated |
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 |
409 | wrong_status | This purchase is refunded and can no longer be marked delivered | The purchase has already reached a final status. status is its current status |
409 | wrong_status | This reward's status has already changed | The purchase changed status while it was being updated. status can be null here |
409 | idempotency_key_reused | This idempotencyKey was already used for a different request | Your API key already used this idempotencyKey for a different request: a request of another kind, or this one with other values. Nothing was changed |
409 | idempotency_in_progress | The first request with this idempotencyKey is still running. Try again in a few seconds. | The first request with this key is still being processed. Wait retryAfter seconds and retry |
502 | bot_unavailable | The delivery could not be finished. Try again | CEEJAY's bot could not be reached or could not finish. Retry the same request: it is safe to repeat |
503 | idempotency_unavailable | idempotencyKey cannot be used on this community yet, so nothing was changed | You sent a key, and this instance does not support idempotency keys yet. Nothing was changed. Community details reports it as features.retryKeys |
504 | bot_timeout | The bot did not answer in time. Send the same request again: it is safe to repeat | CEEJAY's bot did not respond in time. Retry the same request: it is safe to repeat |
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.

