Skip to main content

Create Cross-sell

Append a cross-sell on the owner product. The server mints id and appends at the end. product_id and variant_id are required — the offered product and variant, not the owner.

Defaults when omitted: active=true, one_purchase=true, send_offer=true, discount=null, validity_minutes=15, copy fields null, parent_id=null. Send validity_minutes: null for no timer. Do not send amount or currency.

Live writes return 409 when the product has has_draft: true — see Live writes while a draft exists. Draft writes require draft_version and return it plus preview_url.

Request

Live:

  • POST /products/{productId}/cross-sells

Draft:

  • POST /products/{productId}/draft/cross-sells

Path Parameters

ParameterTypeRequiredDescription
productIdstringYesOwner product UUID.

Request Body

ParameterTypeRequiredDescription
product_idstringYesOffered product UUID.
variant_idstringYesOffered variant UUID.
parent_idstringNoParent cross-sell UUID. Omit or null is a root.
titlestringNoOffer headline. Max 255.
main_descriptionstringNoThank-you body.
descriptionstringNoStored override description.
button_textstringNoCall-to-action label. Max 100.
discountobjectNoNested discount. Omit or null for none. See Discount.
validity_minutesintegerNoThank-you window in minutes. Omit stores 15. null means no timer. Minimum 1 when set.
one_purchasebooleanNoDefaults to true.
send_offerbooleanNoDefaults to true.
activebooleanNoDefaults to true.
draft_versionintegerDraft onlyCurrent overlay version. Minimum 1. A mismatch returns 409.

Unknown keys, including amount and currency, return 422.

Example Request

curl -X POST "https://cart.easy.tools/api/v1/products/0632bef5-c308-42cd-9cba-89a7a4f722bb/cross-sells" \
-H "Authorization: Bearer YOUR_API_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"product_id": "70dbd819-fac2-4dcf-b655-fb925e24410b",
"variant_id": "5272bfcf-4e92-458d-b513-80ca474a16e4",
"title": "Add the workbook",
"button_text": "Yes, add it"
}'

Draft create adds "draft_version": 15 to the body and uses the /draft/cross-sells path.

Response

Success Response (201)

Live returns the Cross-sell Object. Draft returns that object plus draft_version and preview_url.

{
"id": "37643a70-be0f-4f09-ae36-c36e3ddb35dc",
"product_id": "70dbd819-fac2-4dcf-b655-fb925e24410b",
"variant_id": "5272bfcf-4e92-458d-b513-80ca474a16e4",
"parent_id": null,
"title": "Add the workbook",
"main_description": null,
"description": null,
"button_text": "Yes, add it",
"amount": 4900,
"currency": "usd",
"discount": null,
"validity_minutes": 15,
"one_purchase": true,
"send_offer": true,
"active": true
}

Error Responses

Bad Request (400)

{
"message": "Invalid product ID"
}

Not Found Error (404)

{
"message": "Product with ID 0632bef5-c308-42cd-9cba-89a7a4f722bb not found"
}

Conflict Error (409 — Unpublished Draft)

Live write while has_draft is true:

{
"message": "This product has an unpublished draft. Publish, unpublish, or delete the draft before making live changes."
}

Conflict Error (409 — Stale Draft Version)

Draft write when draft_version does not match:

{
"message": "The draft was edited elsewhere. GET the draft and retry with the current draft_version."
}

Validation Error (422 Unprocessable Entity)

Returned when parent_id would exceed depth 3 or form a cycle, the variant does not belong to product_id, discount rules fail, amount or currency is sent, validity_minutes is 0, the owner product is not on Stripe (live only), or another field rule fails. See Discount, Parent and depth, and Offered product and variant.

Named-field failures use { message, errors }:

{
"message": "The given data was invalid.",
"errors": {
"parent_id": ["Cross-sells cannot be nested more than 3 levels deep."]
}
}

Failures that do not belong to one field return a flat { message }. A live write while the owner product is not on Stripe:

{
"message": "Cross-sells cannot be saved until this product is on Stripe."
}

A write the store could not save:

{
"message": "This cross-sell could not be saved."
}

Unauthorized Error (401)

{
"message": "Unauthenticated."
}

Forbidden Error (403)

{
"message": "You do not have API access to the requested store."
}