Returning.AIDevelopers
v1

API reference / Store / Products

.md

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.

Last updated 26 Sep 2026API v1

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#

Authorization#stringREQUIRED
Community API key with store.

RuleBearer <API_KEY>

Content-Type#stringREQUIRED
Request body format.

Ruleapplication/json

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.

name#stringREQUIRED
Product name shown in the Store.

Rule1-100 characters

Eg"$25 Trading Credit"

description#stringREQUIRED
Product description. HTML is allowed; only 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>"

image#stringREQUIRED
Image URL, or null for no image.

Rulehttps:// URL or null

Eg"https://cdn.example.com/store/trading-credit-25.png"

price#numberREQUIRED
Price in coins.

RuleNumber, 0 or more

Eg500

categoryName#stringREQUIRED
Exact name of an existing category in your community, from List Store categories. null for no category.

RuleExisting category name or null

Eg"Trading rewards"

vouchers#string[]REQUIRED
Voucher codes to load as stock, one unit each. Send [] to add none. Codes are trimmed and must be unique within the request.

RuleArray of non-empty strings

Eg["TC25-7Q4M-2B9X"]

voucherExpireDate#stringREQUIRED
Expiry date for every code in 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"

isArchived#booleanREQUIRED
false to show the product. true keeps it out of List products and the Store.

Egfalse

redemptionInstructions#stringREQUIRED
Redemption instructions for the trader, as text or HTML, or null.

RuleString or null

Eg"<p>Your credit is added to your trading account after approval.</p>"

discountPrice#numberREQUIRED
Discounted price in coins, charged between the discount dates while isDiscountEnabled is true. Send 0 when there is no discount.

Rule0 or more; below price when the discount is on

Eg0

isDiscountEnabled#booleanREQUIRED
Switches the discount on or off.

Ruletrue needs both discount dates

Egfalse

discountStartDate#stringREQUIRED
When the discount starts, or null.

RuleISO 8601 date or null; before discountEndDate

Egnull

discountEndDate#stringREQUIRED
When the discount ends, or null.

RuleISO 8601 date or null; after discountStartDate

Egnull

userInformation#objectREQUIRED
What to ask the trader for when they redeem. Send all four flags.

Eg{ ... }

isEnabled#booleanREQUIRED
true to ask for details.

Egfalse

shouldCollectName#booleanREQUIRED
Ask for the trader's name.

Egfalse

shouldCollectPhone#booleanREQUIRED
Ask for the trader's phone number.

Egfalse

shouldCollectAddress#booleanREQUIRED
Ask for the trader's address.

Egfalse

shouldDisplayRemainingQuantity#booleanREQUIRED
true to show traders how many are left.

Egtrue

shouldPrioritiesExpiringVouchers#booleanREQUIRED
true to prioritise the vouchers closest to expiry.

Egfalse

expiringVoucherHighlight#objectOPTIONAL
Highlight for vouchers close to expiry. Omit it to leave the highlight off; when you send it, send all three fields.

Eg{"isEnabled": false, "duration": 7, "timeframe": "days"}

isEnabled#booleanREQUIRED
true to turn the highlight on.

Egfalse

duration#integerREQUIRED
How close to expiry, in timeframe units.

RuleWhole number, 0 or more

Eg7

timeframe#stringREQUIRED
Unit for duration, such as days.

RuleString

Eg"days"

isProductAccessEnabled#booleanREQUIRED
true to apply the rules in productPermission; false to follow the category. Saved as false when productPermission is empty.

Egfalse

productPermission#object[]REQUIRED
Access rules, one entry per rule. Send [] for none. Names are matched in your community.

RuleArray

Eg[]

isEnabled#booleanREQUIRED
true when the rule is active.

Egtrue

type#stringREQUIRED
What the rule matches.

Ruleuser, role, tag or role_combination

Eg"role"

permission#objectOPTIONAL
The traders the rule covers, by name. Optional; when you send it, send all five fields.
roleNames#string[]REQUIRED
Exact role names. [] for none.

RuleExisting role names

tagNames#string[]REQUIRED
Exact tag names. [] for none.

RuleExisting tag names

usernames#string[]REQUIRED
Usernames of traders, matched exactly and case-sensitively. A trader created with Create User may not match until they have signed in once; until then the request returns 400 with Unable to resolve usernames: <names>. [] for none.

RuleExisting usernames; case-sensitive

combination#object[]REQUIRED
Role or tag groups a trader must hold together. [] for none.
type#stringREQUIRED
Whether values are role or tag names.

Rulerole or tag

Eg"role"

values#string[]REQUIRED
Role or tag names in the group.
isSelectAll#booleanREQUIRED
true when the rule covers all traders. Required whenever you send permission.

Egfalse

purchaseAccess#objectOPTIONAL
Special price for traders the rule covers. Omit it for no special price.
specialPrice#numberREQUIRED
Special price in coins.

Rule0 or more

Eg80

isSpecialPriceEnabled#booleanREQUIRED
true to charge the special price.

Egfalse

purchaseLimit#objectOPTIONAL
Purchase limit for traders the rule covers. Omit it for no limit.
isPurchaseLimitEnabled#booleanREQUIRED
true to apply the limit.

Egfalse

quantity#numberREQUIRED
Purchases allowed in each window.

RuleWhole number, 0 or more

Eg1

intervalCount#numberREQUIRED
Length of the window, in intervalUnits.

RuleWhole number, 1 or more

Eg1

intervalUnit#stringREQUIRED
Unit of the window.

Ruledays, weeks, months or years

Eg"months"

accessLevel#stringREQUIRED
What covered traders can do. null means full access.

Rulefull-access, view-only, access-denied or null

Eg"full-access"

isExclusiveTagEnabled#booleanREQUIRED
true to show the exclusive tag.

Egfalse

purchaseStatusOverrideName#stringREQUIRED
Name of the order status new orders for this product start in, or 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.

meta#objectALWAYS
Status details.

Eg{ ... }

status#stringALWAYS
Result of the request.

Rulesuccess

statusCode#integerALWAYS
The HTTP status, repeated.

Eg201

code#stringALWAYS
Machine-readable result code.

RulePRODUCT_CREATED

message#stringALWAYS
Human-readable summary. Do not branch on it.

Eg"Create product success."

data#objectALWAYS
The saved product.

Eg{ ... }

_id#stringALWAYS
Product ID. Save it: you need it to read or update the product.

Eg"66f000000000000000000101"

name#stringALWAYS
Product name shown in the Store.

Eg"$25 Trading Credit"

price#numberALWAYS
Price in coins.

Eg500

categoryID#stringALWAYS
Category ID, or null when the product has no category.
category#stringALWAYS
Category name, or null when the product has no category or its category was deleted.

Eg"Trading rewards"

stocks#integerALWAYS
Vouchers left to buy: codes that are unsold and not expired. Voucher codes are never returned.

Eg1

isArchived#boolean
true when the product is archived and left out of List products and the Store.

Egfalse

image#string
Image URL, or null when there is no image.

Eg"https://cdn.example.com/store/trading-credit-25.png"

description#string
Product description as HTML. Only basic formatting tags are kept.

Eg"<p>Redeem 500 coins for a $25 trading credit on your live account.</p>"

redemptionInstructions#string
Redemption instructions for the trader, or null.

Eg"<p>Your credit is added to your trading account after approval.</p>"

discountPrice#number
Discounted price in coins, charged between the discount dates while the discount is on.

Eg0

isDiscountEnabled#boolean
true when the discount is switched on.

Egfalse

discountStartDate#string
When the discount starts, ISO 8601 UTC, or null.

Egnull

discountEndDate#string
When the discount ends, ISO 8601 UTC, or null.

Egnull

userInformation#object
What the trader is asked for when they redeem.

Eg{ ... }

isEnabled#boolean
true when the trader is asked for details.

Egfalse

shouldCollectName#boolean
true to ask for their name.

Egfalse

shouldCollectPhone#boolean
true to ask for their phone number.

Egfalse

shouldCollectAddress#boolean
true to ask for their address.

Egfalse

shouldDisplayRemainingQuantity#boolean
true to show traders how many are left.

Egtrue

shouldPrioritiesExpiringVouchers#boolean
true to prioritise the vouchers closest to expiry.

Egfalse

expiringVoucherHighlight#object
Highlight for vouchers close to expiry.

Eg{"isEnabled": false, "duration": 7, "timeframe": "days"}

isEnabled#boolean
true when the highlight is on.

Egfalse

duration#integer
How close to expiry, in timeframe units.

Eg7

timeframe#string
Unit for duration, such as days.

Eg"days"

isProductAccessEnabled#boolean
true when the product's own rules apply; false when it follows its category.

Egfalse

productPermission#object[]ALWAYS
Access rules. An empty array means the product has no rules of its own.

Eg[]

isEnabled#boolean
true when the rule is active.

Egtrue

type#string
What the rule matches.

Ruleuser, role, tag or role_combination

Eg"role"

permission#object
The traders the rule covers, by readable name.
roleNames#string[]
Role names.
tagNames#string[]
Tag names.
usernames#string[]
Usernames.
userIDs#string[]
Record IDs of the traders the rule names. Send usernames when you write rules.
combination#object[]
Role or tag groups a trader must hold together.
type#string
role or tag.

Eg"role"

values#string[]
Role or tag names in the group.
isSelectAll#boolean
true when the rule covers all traders.
purchaseAccess#object
Special price for traders the rule covers.
specialPrice#number
Special price in coins.

Eg80

isSpecialPriceEnabled#boolean
true when the special price applies.

Egfalse

purchaseLimit#object
How often a covered trader can buy.
isPurchaseLimitEnabled#boolean
true when the limit applies.

Egfalse

quantity#number
Purchases allowed in each window.

Eg1

intervalCount#number
Length of the window, in intervalUnits.

Eg1

intervalUnit#string
Unit of the window, such as months.

Eg"months"

accessLevel#string
What covered traders can do.

Rulefull-access, view-only or access-denied

Eg"full-access"

isExclusiveTagEnabled#boolean
true to show the exclusive tag.

Egfalse

purchaseStatusOverride#objectALWAYS
The status new orders for this product start in, when set.

Eg{"isEnabled": false, "status": null}

isEnabled#boolean
true when new orders start in status.

Egfalse

status#string
Order status name, or null when the category or community default applies.

Egnull

totalOrders#integer
Orders placed for this product.

Eg0

communityID#string
Your community ID.

Eg"66f000000000000000000010"

createdAt#string
When the product was created, ISO 8601 UTC.

Eg"2026-09-26T08:30:00.000Z"

updatedAt#string
When the product last changed, ISO 8601 UTC.

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

400VALIDATION_FAILEDFix the request
A field is missing or breaks a rule, or a name has no match: an unknown category, role, tag, username or status, an empty or repeated voucher code, or codes without voucherExpireDate. detail says which. Nothing was saved.
401AUTH_API_KEY_REQUIREDFix the request
No key was sent. Send Authorization: Bearer <API_KEY>.
401AUTH_API_KEY_INVALIDFix the request
The key is unknown, expired or malformed. Use a current Community API key.
403AUTH_PERMISSION_REQUIREDFix the request
The key lacks store. Add the permission in Settings > Integration > API Keys.

Retry with backoff03

500INTERNAL_ERRORRetry with backoff
The outcome is unclear, and the product may exist without its vouchers. Search List products by name before you create it again.
502STORE_DEPENDENCY_UNAVAILABLERetry with backoff
The Store was unreachable. Search List products by name, then retry with backoff if the product isn't there.
401AUTH_API_KEY_VALIDATION_FAILEDRetry with backoff
The key could not be checked just now. Retry with backoff; the key itself may be fine.
{
  "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."
}

Next step#

Read productGET/v1/products/{productID}Read the product back with its new _id to confirm the price, category and stocks.