Update products in bulk
Change up to 100 Store products in one call, sending only the fields that change for each, from a JSON array or a CSV file.
- Method
- PUT
- Path
https://api.returning.ai / v1/ products/ bulk - Permission
- store
- Retries
- All-or-nothing; re-sent codes return 409
When to use this
- You reprice or archive many products at once.
- You restock several products with new voucher codes in one request.
- Your team keeps the catalogue in a spreadsheet and uploads it as CSV.
Authentication#
- Header
Authorization: Bearer <API_KEY>- Permission
- storeShown in the dashboard as “Store”
Send a Community API key with the store permission. The key decides the community, so never send a community ID. Keep the key on your server.
Behaviour#
Every item is checked before anything is saved, then all the products and codes are saved together: if any item fails, nothing changes. Fields an item leaves out keep their current values. New codes are added as stock; existing codes are never removed or replaced.
Request#
Headers#
curl --request PUT \
--url https://api.returning.ai/v1/products/bulk \
--header 'Authorization: Bearer <API_KEY>' \
--header 'Content-Type: application/json' \
--data '[
{
"productID": "66f000000000000000000101",
"price": 450
}
]'
Response#
A 200 returns the updated products in data, in the order you sent them, and meta.updated counts them. stocks includes any codes you added, unless they have already expired. Branch on the HTTP status and meta.code, never on message.
Eg{ ... }
Rulesuccess
Eg200
RulePRODUCTS_BULK_UPDATED
Eg1
Eg"Update products success."
Eg[ ... ]
Eg"66f000000000000000000101"
Eg"$25 Trading Credit"
Eg450
null when the product has no category.null when the product has no category or its category was deleted.Eg"Trading rewards"
Eg2
true when the product is archived and left out of List products and the Store.Egfalse
null when there is no image.Eg"https://cdn.example.com/store/trading-credit-25.png"
Eg"<p>Redeem 500 coins for a $25 trading credit on your live account.</p>"
null.Eg"<p>Your credit is added to your trading account after approval.</p>"
Eg0
true when the discount is switched on.Egfalse
null.Egnull
null.Egnull
Eg{ ... }
true when the trader is asked for details.Egfalse
true to ask for their name.Egfalse
true to ask for their phone number.Egfalse
true to ask for their address.Egfalse
true to show traders how many are left.Egtrue
true to prioritise the vouchers closest to expiry.Egfalse
Eg{"isEnabled": false, "duration": 7, "timeframe": "days"}
true when the highlight is on.Egfalse
timeframe units.Eg7
duration, such as days.Eg"days"
true when the product's own rules apply; false when it follows its category.Egfalse
Eg[]
true when the rule is active.Egtrue
Ruleuser, role, tag or role_combination
Eg"role"
usernames when you write rules.role or tag.Eg"role"
true when the rule covers all traders.Eg80
true when the special price applies.Egfalse
true when the limit applies.Egfalse
Eg1
intervalUnits.Eg1
months.Eg"months"
Rulefull-access, view-only or access-denied
Eg"full-access"
true to show the exclusive tag.Egfalse
Eg{"isEnabled": false, "status": null}
true when new orders start in status.Egfalse
null when the category or community default applies.Egnull
Eg0
Eg"66f000000000000000000010"
Eg"2026-09-26T08:30:00.000Z"
Eg"2026-09-26T10:00:00.000Z"
{
"meta": {
"status": "success",
"statusCode": 200,
"updated": 1,
"code": "PRODUCTS_BULK_UPDATED"
},
"message": "Update products success.",
"data": [
{
"_id": "66f000000000000000000101",
"communityID": "66f000000000000000000010",
"name": "$25 Trading Credit",
"description": "<p>Redeem 500 coins for a $25 trading credit on your live account.</p>",
"image": "https://cdn.example.com/store/trading-credit-25.png" ,
"price": 450,
"categoryID": "66f000000000000000000201",
"category": "Trading rewards",
"stocks": 2,
"discountPrice": 0,
"isDiscountEnabled": false,
"discountStartDate": null,
"discountEndDate": null,
"userInformation": {
"isEnabled": false,
"shouldCollectName": false,
"shouldCollectPhone": false,
"shouldCollectAddress": false
},
"shouldPrioritiesExpiringVouchers": false,
"shouldDisplayRemainingQuantity": true,
"expiringVoucherHighlight": {
"isEnabled": false,
"duration": 7,
"timeframe": "days"
},
"isArchived": false,
"totalOrders": 0,
"redemptionInstructions": "<p>Your credit is added to your trading account after approval.</p>",
"productPermission": [],
"isProductAccessEnabled": false,
"purchaseStatusOverride": {
"isEnabled": false,
"status": null
},
"createdAt": "2026-09-26T08:30:00.000Z",
"updatedAt": "2026-09-26T10:00:00.000Z"
}
]
}
Errors#
Every error carries its code in meta.code, with the reason in detail as a sentence. An item that breaks a rule is named by position, such as Bulk product item 2 validation failed:, and a CSV error names its row.
Fix the request04
VALIDATION_FAILEDFix the requestproductID, or an item breaks a rule or names something with no match. detail says which. Nothing was saved.AUTH_API_KEY_REQUIREDFix the requestAuthorization: Bearer <API_KEY>.AUTH_API_KEY_INVALIDFix the requestAUTH_PERMISSION_REQUIREDFix the requeststore. Add the permission in Settings > Integration > API Keys.Fix the data02
STORE_RESOURCE_NOT_FOUNDFix the dataproductID isn't in your community, so nothing was saved. Correct or remove that item and send the batch again.STORE_RESOURCE_CONFLICTFix the datavouchers has codes that product already has. Nothing was saved; drop those codes and send again.Retry with backoff03
INTERNAL_ERRORRetry with backoffSTORE_DEPENDENCY_UNAVAILABLERetry with backoffAUTH_API_KEY_VALIDATION_FAILEDRetry with backoff{
"meta": {
"status": "error",
"statusCode": 400,
"code": "VALIDATION_FAILED"
},
"message": "Update products error.",
"detail": "Bulk product JSON body must be a raw array, not an object wrapper.",
"solution": "Check the request parameters and try again."
}