Create product with vouchers
Create one Store product in the community that owns your API key, and load its voucher codes as stock in the same call.
- Method
- POST
- Path
https://api.returning.ai / v1/ products - Permission
- store
- Retries
- No Idempotency-Key; check before retrying
When to use this
- You add a reward to your Store, such as a trading credit, and have the voucher codes ready.
- You set up a new Store from your own catalogue, one product at a time.
- You want the product's category, price and access rules set by name, without looking up IDs.
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#
The product is saved first, then its voucher codes are added as stock. Unless isArchived is true, the product is live as soon as the response returns: it appears in List products and in your Store, unless its category is archived or access rules hide it from a trader.
Descriptions keep only basic formatting tags. Voucher codes are trimmed, and a code only has to be unique within its product. With an empty productPermission, isProductAccessEnabled is saved as false.
Request#
Headers#
Body#
Every field is required except expiringVoucherHighlight; send null where a field allows it. Unknown fields are ignored. Call List Store categories first and copy the category name exactly. Put the codes in vouchers with one voucherExpireDate for all of them; each code is one unit of stock.
Rule1-100 characters
Eg"$25 Trading Credit"
p, br, strong, b, em, i, u, span and a tags are kept. Send "" for none.RuleString, may be empty
Eg"<p>Redeem 500 coins for a $25 trading credit on your live account.</p>"
null for no image.Rulehttps:// URL or null
Eg"https://cdn.example.com/store/trading-credit-25.png"
RuleNumber, 0 or more
Eg500
null for no category.RuleExisting category name or null
Eg"Trading rewards"
[] to add none. Codes are trimmed and must be unique within the request.RuleArray of non-empty strings
Eg["TC25-7Q4M-2B9X"]
vouchers. Required when vouchers has codes; send "" when it is empty. Codes that have already expired are saved but don't count as stock.RuleISO 8601 date, or ""
Eg"2026-12-31T23:59:59.000Z"
false to show the product. true keeps it out of List products and the Store.Egfalse
null.RuleString or null
Eg"<p>Your credit is added to your trading account after approval.</p>"
isDiscountEnabled is true. Send 0 when there is no discount.Rule0 or more; below price when the discount is on
Eg0
Ruletrue needs both discount dates
Egfalse
null.RuleISO 8601 date or null; before discountEndDate
Egnull
null.RuleISO 8601 date or null; after discountStartDate
Egnull
Eg{ ... }
true to ask for details.Egfalse
Egfalse
Egfalse
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 to turn the highlight on.Egfalse
timeframe units.RuleWhole number, 0 or more
Eg7
duration, such as days.RuleString
Eg"days"
true to apply the rules in productPermission; false to follow the category. Saved as false when productPermission is empty.Egfalse
[] for none. Names are matched in your community.RuleArray
Eg[]
true when the rule is active.Egtrue
Ruleuser, role, tag or role_combination
Eg"role"
[] for none.RuleExisting role names
[] for none.RuleExisting tag names
400 with Unable to resolve usernames: <names>. [] for none.RuleExisting usernames; case-sensitive
[] for none.values are role or tag names.Rulerole or tag
Eg"role"
true when the rule covers all traders. Required whenever you send permission.Egfalse
Rule0 or more
Eg80
true to charge the special price.Egfalse
true to apply the limit.Egfalse
RuleWhole number, 0 or more
Eg1
intervalUnits.RuleWhole number, 1 or more
Eg1
Ruledays, weeks, months or years
Eg"months"
null means full access.Rulefull-access, view-only, access-denied or null
Eg"full-access"
true to show the exclusive tag.Egfalse
null to use the category or community default.RuleExisting status name or null
Egnull
curl --request POST \
--url https://api.returning.ai/v1/products \
--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,
"expiringVoucherHighlight": {
"isEnabled": false,
"duration": 7,
"timeframe": "days"
},
"isProductAccessEnabled": false,
"productPermission": [],
"purchaseStatusOverrideName": null
}'
Response#
A 201 returns the saved product in data. Save data._id: every other product call uses it. stocks counts the codes you sent, 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
RulePRODUCT_CREATED
Eg"Create product 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": 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-26T08:30:00.000Z"
{
"meta": {
"status": "success",
"statusCode": 201,
"code": "PRODUCT_CREATED"
},
"message": "Create product 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": 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-26T08:30:00.000Z"
}
}
Errors#
Every error carries its code in meta.code, with the reason in detail. When the body fails validation, detail is an object keyed by field, such as productPermission.0.type; when a name has no match or a voucher rule fails, detail is a sentence. 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 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.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 product error.",
"detail": "Category not found for name: Trading prizes",
"solution": "Check your input parameters and try again."
}