Sell a product
Create a hidden product, add a variant, and configure a recurring price.
A product is what the customer sees; a variant is the option they buy. The product holds the title and description, while each variant sets the price, payment methods, stock, and delivery. This example creates Founder memo circle, with a $19.99 monthly membership purchased by Natya in the checkout walkthrough.
1. Prepare credentials
Use Bash, cURL, and jq. Replace the key and store slug below. Your token needs
the listing ability and corresponding store permissions. These are real
records, not sandbox data; use a dedicated store with Stripe subscriptions
configured. No payment is collected by these catalog requests, but recurring
pricing can create provider objects.
set -euo pipefail
export SELLAPP_API_BASE_URL='https://sell.app/api'
export SELLAPP_API_KEY='replace-me'
export SELLAPP_STORE_SLUG='launch-lab'
api() {
local method="$1" path="$2"
shift 2
curl --silent --show-error --fail-with-body \
--request "$method" --url "${SELLAPP_API_BASE_URL}${path}" \
--header "Authorization: Bearer ${SELLAPP_API_KEY}" \
--header "X-STORE: ${SELLAPP_STORE_SLUG}" \
--header 'Accept: application/json' \
--dump-header /dev/stderr "$@"
}
api GET '/v2/products?limit=1' | jq '.data'2. Create the product and variant
HIDDEN keeps the product off normal storefront listings; it is not an
is_draft flag or a security boundary. The API rejects writes to is_draft.
Do not share checkout links until setup is complete.
product_json=$(api POST /v2/products \
--header 'Content-Type: application/json' \
--data '{
"title":"Founder memo circle",
"description":"A monthly collection of annotated operating memos.",
"visibility":"HIDDEN"
}')
SELLAPP_PRODUCT_ID=$(jq -er '.data.id' <<< "$product_json")
variant_json=$(api POST "/v2/products/${SELLAPP_PRODUCT_ID}/variants" \
--header 'Content-Type: application/json' \
--data '{
"title":"Monthly membership",
"description":"One operating memo each month; access is provisioned by our team.",
"deliverable":{"types":["MANUAL"],"data":{"stock":null,"comment":"We will send your reading-room invitation."}},
"pricing":{"humble":false,"price":{"price":1999,"currency":"USD"}},
"payment_methods":["STRIPE"]
}')
SELLAPP_VARIANT_ID=$(jq -er '.data.id' <<< "$variant_json")Both requests return 201. Selected fields from their responses:
{"data":{"id":120,"title":"Founder memo circle","visibility":"HIDDEN","variants":[]}}{"data":{"id":4321,"product_id":120,"title":"Monthly membership","payment_methods":["STRIPE"]}}3. Configure monthly recurring pricing
The variant starts with a one-time price. This next request changes it to a
monthly subscription. USD amounts use cents: send 1999 for $19.99, not 19.99.
api PUT "/v2/products/${SELLAPP_PRODUCT_ID}/variants/${SELLAPP_VARIANT_ID}/pricing" \
--header 'Content-Type: application/json' \
--data '{
"pricing":{
"type":"SUBSCRIPTION",
"humble":false,
"price":{"price":1999,"currency":"USD"},
"frequency":{"value":1,"interval":"MONTH"}
},
"payment_methods":["STRIPE"]
}' | jq -e '.data.pricing.type == "SUBSCRIPTION"'Expect 200. Resolve provider/configuration errors before continuing; do not
create the invoice with the initial one-time price by accident. The MANUAL
deliverable means your team or integration must give the customer reading-room access;
a successful API response does not do that work for you.
4. Verify the result
api GET "/v2/products/${SELLAPP_PRODUCT_ID}" | jq '.data | {id, title, visibility}'
api GET "/v2/products/${SELLAPP_PRODUCT_ID}/variants/${SELLAPP_VARIANT_ID}" \
| jq '.data | {id, title, pricing, payment_methods}'Keep the IDs from your responses; 120 and 4321 are examples, not test resources you can reuse.
Send the most recently read updated_at as expected_updated_at when updating
the product to catch changes made since you last read it. Sensitive deliverables
are hidden or write-only, so keep your own secure copy; a read response is not a backup.
Creation has no general idempotency key. After a lost response, check whether the product or variant was created before repeating a POST. Keep the product hidden until you intend to list it, then change visibility deliberately.
Next, create a checkout, registering and testing your webhook before invoice creation. For all steps in one page, use the complete quickstart.