curl -X POST https://api.khaime.com/api/v1/marketplace/products/501/variants \
-H "X-API-Key: pk_sandbox_your_key" \
-H "Content-Type: application/json" \
-d '{
"variation_type": "size",
"variation_custom_label": "Size",
"has_sub_variants": false,
"variations": [
{
"id": "v-11111111-1111-1111-1111-111111111111",
"sku": "SHIRT-M",
"variant_label": "Medium",
"variant_object": "{\"size\":\"M\"}",
"variation_quantity": 20,
"variation_add_price": 0,
"order_index": 0
},
{
"id": "v-22222222-2222-2222-2222-222222222222",
"sku": "SHIRT-L",
"variant_label": "Large",
"variant_object": "{\"size\":\"L\"}",
"variation_quantity": 20,
"variation_add_price": 200,
"order_index": 1
}
]
}'
Marketplace
Create/Replace Product Variants
Fully replace the variant tree for a product — this is a destructive full-sync, not an incremental add.
POST
/
marketplace
/
products
/
{productId}
/
variants
curl -X POST https://api.khaime.com/api/v1/marketplace/products/501/variants \
-H "X-API-Key: pk_sandbox_your_key" \
-H "Content-Type: application/json" \
-d '{
"variation_type": "size",
"variation_custom_label": "Size",
"has_sub_variants": false,
"variations": [
{
"id": "v-11111111-1111-1111-1111-111111111111",
"sku": "SHIRT-M",
"variant_label": "Medium",
"variant_object": "{\"size\":\"M\"}",
"variation_quantity": 20,
"variation_add_price": 0,
"order_index": 0
},
{
"id": "v-22222222-2222-2222-2222-222222222222",
"sku": "SHIRT-L",
"variant_label": "Large",
"variant_object": "{\"size\":\"L\"}",
"variation_quantity": 20,
"variation_add_price": 200,
"order_index": 1
}
]
}'
Intro
Sets the complete variant tree (variations → optional sub-variations → sub-variants) for a product in one call — a full replace, not an incremental add.Context
Pairs with Get Product Variants, which returns the tree this endpoint writes, and with Marketplace Products, whose product record this endpoint updates (has_variation, total_quantity). It operates on any product you own or that belongs to an active sub-merchant in your marketplace — the same ownership rule used across the product endpoints.
Hows
Orphan cleanup is real and destructive. If you fetch the current tree via Get Variants, remove one variant client-side, and POST the rest, the removed variant (and any of its sub-variants) is permanently deleted server-side — there’s no soft-delete or undo. Always send the entire desired tree, not a partial update.
Path Parameters
integer
required
A product you own, or a product owned by an active sub-merchant in your marketplace (marketplace operators can manage sub-merchant product variants directly).
Request Body
string
required
Label for the top-level variation axis, e.g.
"size".string
Display label shown to customers. Defaults to
variation_type if omitted.boolean
Whether each variation has a nested sub-variation axis (e.g. size → color). Recalculated server-side after processing — the server checks whether any
variations[].sub_variation.sub_variants actually has entries and overwrites whatever you sent with that computed value. Sending true with no real sub-variants will be silently corrected to false.integer
default:"0"
Only used if you provide it and it’s truthy — otherwise it’s computed server-side as the sum of each variation’s
variation_quantity. Either way, a second pass then recomputes the true total from the full tree (including sub-variant quantities) and writes that final number back to both the variation tree and the product record — so don’t rely on the value you sent surviving unchanged.array
required
Non-empty array. Each item:
Show Variation item fields
Show Variation item fields
string
required
You must generate this UUID client-side — it is not server-assigned. Reuse the same
id on subsequent POSTs to update rather than replace a specific node; omit it (or use a new one) to create.If any
variation.id (or nested sub_variation/sub_variants id) is missing, the whole request fails — but the response only ever says "Failed to save variants" with no field-level detail. The actual validation reason (“Variation ID is required”) is logged server-side only, not returned to you. Double-check every node has an id before sending.string
SKU for this variant.
string
Display label, e.g.
"Medium".string
JSON-encoded string (not a nested object) describing the variant’s attributes, e.g.
"{\"size\":\"M\"}".integer
Stock quantity at this level.
integer
Price delta in smallest currency unit, added to the base product price.
integer
default:"0"
Cost price in smallest currency unit.
integer
Display order. Defaults to array index if omitted.
object
One nested sub-variation axis (e.g. color within size). Same
id-required rule applies to it and its sub_variants.Response
The response
data here is a flat ProductVariation record — it does not include the nested variations/sub_variation/sub_variants tree. If you need the full tree back (e.g. to get server-confirmed nesting), call Get Variants right after. This asymmetry between POST and GET response shapes is intentional in the current implementation, not a bug — don’t rely on the POST response for anything beyond id/total_quantity.{
"success": true,
"message": "Variants saved successfully",
"data": {
"id": "b6b6c2b0-...-uuid",
"product_id": 501,
"variation_type": "size",
"variation_custom_label": "Size",
"has_sub_variants": true,
"total_quantity": 40
}
}
Side Effects
- Recomputes and overwrites
total_quantityon both the variation tree and the parent product record. - Sets the product’s
has_variationflag totrueunconditionally, even ifvariationsends up empty after processing. - Deletes any variant/sub-variation/sub-variant rows not present in this request’s
variationsarray (see warning above).
Error Codes
| Status | Error Code | Cause |
|---|---|---|
404 | PRODUCT_NOT_FOUND | Product doesn’t exist, or you don’t own it and aren’t its marketplace operator. (Deliberately not distinguished from “doesn’t exist” — no ownership info leak.) |
400 | VALIDATION_MISSING_FIELD | variation_type missing, or variations missing/empty. |
400 | INTERNAL_ERROR | Generic failure — includes the missing-id case above; check every node has an id before retrying. |
curl -X POST https://api.khaime.com/api/v1/marketplace/products/501/variants \
-H "X-API-Key: pk_sandbox_your_key" \
-H "Content-Type: application/json" \
-d '{
"variation_type": "size",
"variation_custom_label": "Size",
"has_sub_variants": false,
"variations": [
{
"id": "v-11111111-1111-1111-1111-111111111111",
"sku": "SHIRT-M",
"variant_label": "Medium",
"variant_object": "{\"size\":\"M\"}",
"variation_quantity": 20,
"variation_add_price": 0,
"order_index": 0
},
{
"id": "v-22222222-2222-2222-2222-222222222222",
"sku": "SHIRT-L",
"variant_label": "Large",
"variant_object": "{\"size\":\"L\"}",
"variation_quantity": 20,
"variation_add_price": 200,
"order_index": 1
}
]
}'
Whys
Full-replace-on-write (rather than incremental add/update/delete operations) keeps the client’s view of the variant tree and the server’s in lockstep with a single call — the client always sends its complete intended state, and the server makes reality match it, rather than requiring three separate endpoints (add variant, update variant, delete variant) that a client could call out of order or partially. Client-generated UUIDs make this safe for the common editing pattern of “fetch the tree, mutate it locally, send the whole thing back” — reusing anid updates that exact node instead of creating a duplicate.
Recomputing total_quantity and has_sub_variants server-side rather than trusting client-sent values keeps the product’s stock and variant-shape fields consistent with what was actually stored, even if the client’s math was wrong or stale.
Why nots
This is not an incremental/patch operation — sending a subset of the tree deletes everything you left out. It does not return the full tree in its response — call Get Variants if you need to confirm what was actually stored. It does not give you field-level validation errors for a missingid — the response is a generic failure message, so validate that every node has an id before sending rather than relying on the API to tell you which one is missing.