Create products in bulk
Create up to 100 Store products in one call, each with its voucher codes, from a JSON array or a CSV file.
- Method
- POST
- Path
https://api.returning.ai / v1/ products/ bulk - Permission
- store
- Retries
- No Idempotency-Key; check before retrying
When to use this
- You load a new Store catalogue, or a new batch of rewards, and have every product's details and codes ready.
- Your team keeps the catalogue in a spreadsheet and uploads it as CSV.
- You want a batch to go live in full or not at all.
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, and every category, role, tag, username and status name looked up, before anything is saved. Then all the products and their codes are saved together: if any item fails, nothing is created. Unless an item has isArchived: true, its product is live as soon as the response returns.
Each item always creates a new product. Names don't have to be unique, within the batch or against products you already have. Voucher codes are trimmed, and a code only has to be unique within its own item.
Request#
Headers#
curl --request POST \
--url https://api.returning.ai/v1/products/bulk \
--header 'Authorization: Bearer <API_KEY>' \
--header 'Content-Type: application/json' \
--data '[
{
"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": 500,
"categoryName": "Trading rewards",
"vouchers": ["TC25-7Q4M-2B9X"],
"voucherExpireDate": "2026-12-31T23:59:59.000Z",
"isArchived": false,
"redemptionInstructions": "<p>Your credit is added to your trading account after approval.</p>",
"discountPrice": 0,
"isDiscountEnabled": false,
"discountStartDate": null,
"discountEndDate": null,
"userInformation": {
"isEnabled": false,
"shouldCollectName": false,
"shouldCollectPhone": false,
"shouldCollectAddress": false
},
"shouldDisplayRemainingQuantity": true,
"shouldPrioritiesExpiringVouchers": false,
"isProductAccessEnabled": false,
"productPermission": [],
"purchaseStatusOverrideName": null
},
{
"name": "$50 Trading Credit",
"description": "<p>Redeem 1000 coins for a $50 trading credit on your live account.</p>",
"image": "https://cdn.example.com/store/trading-credit-50.png",
"price": 1000,
"categoryName": "Trading rewards",
"vouchers": ["TC50-4K8P-6D2W"],
"voucherExpireDate": "2026-12-31T23:59:59.000Z",
"isArchived": false,
"redemptionInstructions": "<p>Your credit is added to your trading account after approval.</p>",
"discountPrice": 0,
"isDiscountEnabled": false,
"discountStartDate": null,
"discountEndDate": null,
"userInformation": {
"isEnabled": false,
"shouldCollectName": false,
"shouldCollectPhone": false,
"shouldCollectAddress": false
},
"shouldDisplayRemainingQuantity": true,
"shouldPrioritiesExpiringVouchers": false,
"isProductAccessEnabled": false,
"productPermission": [],
"purchaseStatusOverrideName": null
}
]'
Response#
A 201 returns the new products in data, in the order you sent them, and meta.created counts them. Save each _id: every other product call uses it. stocks counts each product's codes, less any that have already expired; voucher codes are never returned. Branch on the HTTP status and meta.code, never on message.
Eg{ ... }
Rulesuccess
Eg201
RulePRODUCTS_BULK_CREATED
Eg2
Eg"Create products success."
Eg[ ... ]
Eg"66f000000000000000000101"
Eg"$25 Trading Credit"
Eg500
null when the product has no category.null when the product has no category or its category was deleted.Eg"Trading rewards"
Eg1
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": 0, "timeframe": "days"}
true when the highlight is on.Egfalse
timeframe units.Eg0
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-26T08:30:00.000Z"
{
"meta": {
"status": "success",
"statusCode": 201,
"created": 2,
"code": "PRODUCTS_BULK_CREATED"
},
"message": "Create 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": 500,
"categoryID": "66f000000000000000000201",
"category": "Trading rewards",
"stocks": 1,
"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": 0,
"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-26T08:30:00.000Z"
},
{
"_id": "66f000000000000000000102",
"communityID": "66f000000000000000000010",
"name": "$50 Trading Credit",
"description": "<p>Redeem 1000 coins for a $50 trading credit on your live account.</p>",
"image": "https://cdn.example.com/store/trading-credit-50.png" ,
"price": 1000,
"categoryID": "66f000000000000000000201",
"category": "Trading rewards",
"stocks": 1,
"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": 0,
"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-26T08:30:00.000Z"
}
]
}
Errors#
Every error carries its code in meta.code, with the reason in detail as a sentence. A rule broken inside an item is named by position, such as Bulk product item 2 validation failed: price must not be less than 0, and a CSV error names its row, such as row 3: Price must be a number. A name with no match or a voucher problem doesn't say which item, for example Category not found for name: Trading prizes. A body that isn't valid JSON returns 400 without a meta.code.
Fix the request04
VALIDATION_FAILEDFix the requestvoucherExpireDate. detail says which. Nothing was created.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.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": "Create products error.",
"detail": "Category not found for name: Trading prizes",
"solution": "Check your input parameters and try again."
}