The short answer on Flint
Flint is a payments API with the commerce system on top, and card processing runs on Stripe underneath. Its catalog has products with options and variants: one Crew tee product, a Size option and a Color option, and a variant for each combination you sell, with its own price, SKU, tax category, and stock. An order line names the variant, Flint copies its name and price onto the order, and payment fails before any money moves if that size is out of stock. Paid add-ons such as gift wrap or engraving are modifier sets with their own prices. The Flint requests are below.
What Stripe tells you to do
Stripe's catalog has two objects. A Product is a name, description, images, a tax code, and metadata. A Price is an amount and currency, and for subscriptions an interval. Stripe's modeling rule is to create a separate product for each distinct option a customer chooses between, and to use several prices on one product only for billing differences such as monthly versus annual or another currency. Its e-commerce example is a product for each size and color combination of a shirt.
The reason is what the buyer sees. Checkout, Payment Links, and invoices print the Product name on each line, so line items that share a product share its name, and an XL and an M bought together would read as the same item with two amounts.
The workaround, in code
Create one Product per combination and keep your own table that says which Product is the sand XL of the crew tee. Stripe has nowhere to store that grouping except metadata on each Product:
const sizes = ["S", "M", "L", "XL"];
const colors = ["Black", "Sand", "Navy"];
for (const color of colors) {
for (const size of sizes) {
const product = await stripe.products.create({
name: `Crew tee, ${color}, ${size}`,
metadata: { style: "crew-tee", color, size },
default_price_data: {
currency: "usd",
unit_amount: size === "XL" ? 3200 : 2800,
},
});
// Stripe has nowhere to group these 12 products, so you keep the map
await db.variants.insert({
style: "crew-tee",
color,
size,
productId: product.id,
priceId: product.default_price,
stock: 0, // and the count, see the stock trap below
});
}
}
Then build the size and color picker on your product page. When the buyer checks out, look up the combination in your table, check your own stock count, and create the session with that one price:
// Your product page posted the buyer's size and color
const variant = await db.variants.findOne({
style: "crew-tee",
color: req.body.color,
size: req.body.size,
});
if (!variant || variant.stock < 1) return res.status(409).send("Sold out");
const session = await stripe.checkout.sessions.create({
mode: "payment",
line_items: [{ price: variant.priceId, quantity: 1 }],
metadata: { variant_key: `crew-tee/${variant.color}/${variant.size}` },
success_url: "https://example.com/thanks?session_id={CHECKOUT_SESSION_ID}",
});
That is the whole integration surface Stripe gives you for variants. Everything that treats the twelve Products as one shirt is code you write and keep in sync: the mapping table, the picker, the stock counts, coupon product lists, and price changes. Third-party tools sell variant pickers for Stripe pages for the same reason; Checkout Page's guide, for one, lists four workarounds and names its own product as one.
Where the workaround costs you
The stock trap has its own lookup: Stripe inventory management: how to prevent overselling.
On Flint, variants are part of the catalog
One request creates the product, its options, and its variants. Each variant selects one value for each option, carries its own price and SKU, and here gets an inventory item so its stock is tracked:
curl -X POST https://api.withflintpay.com/v1/products \
-H "Authorization: Bearer YOUR_API_KEY" \
-H "Idempotency-Key: product-crew-tee" \
-H "Content-Type: application/json" \
-d '{
"name": "Crew tee",
"product_type": "physical",
"options": [
{
"name": "Size",
"values": [
{ "value": "M" },
{ "value": "XL" }
]
},
{
"name": "Color",
"values": [
{ "value": "Black" },
{ "value": "Sand" }
]
}
],
"variants": [
{
"name": "M / Black",
"sku": "TEE-M-BLK",
"unit_price_money": { "amount": 2800, "currency": "USD" },
"line_item_tax_category": "clothing",
"inventory_item": { "name": "Crew tee, M, Black" },
"selected_options": [
{ "option_name": "Size", "value": "M" },
{ "option_name": "Color", "value": "Black" }
]
},
{
"name": "M / Sand",
"sku": "TEE-M-SND",
"unit_price_money": { "amount": 2800, "currency": "USD" },
"line_item_tax_category": "clothing",
"inventory_item": { "name": "Crew tee, M, Sand" },
"selected_options": [
{ "option_name": "Size", "value": "M" },
{ "option_name": "Color", "value": "Sand" }
]
},
{
"name": "XL / Black",
"sku": "TEE-XL-BLK",
"unit_price_money": { "amount": 3200, "currency": "USD" },
"line_item_tax_category": "clothing",
"inventory_item": { "name": "Crew tee, XL, Black" },
"selected_options": [
{ "option_name": "Size", "value": "XL" },
{ "option_name": "Color", "value": "Black" }
]
},
{
"name": "XL / Sand",
"sku": "TEE-XL-SND",
"unit_price_money": { "amount": 3200, "currency": "USD" },
"line_item_tax_category": "clothing",
"inventory_item": { "name": "Crew tee, XL, Sand" },
"selected_options": [
{ "option_name": "Size", "value": "XL" },
{ "option_name": "Color", "value": "Sand" }
]
}
]
}'
Flint never generates variants from options, so you still list each combination you sell, the way you would in a spreadsheet. The difference is that they belong to one product. Two variants with the same combination are rejected with VARIANT_OPTION_COMBINATION_ALREADY_EXISTS, and adding a size later is one write to the product's options and one new variant. List the variants to render your picker:
curl https://api.withflintpay.com/v1/products/prod_1kmn0aExample/variants \
-H "Authorization: Bearer YOUR_API_KEY"
Each one says which option values it is, with no mapping table on your side (abridged):
{
"variant_id": "var_1kmn0aExample",
"product_id": "prod_1kmn0aExample",
"name": "XL / Sand",
"sku": "TEE-XL-SND",
"unit_price_money": { "amount": 3200, "currency": "USD" },
"selected_options": [
{ "option_name": "Size", "value": "XL" },
{ "option_name": "Color", "value": "Sand" }
],
"inventory_tracking": "tracked",
"inventory_item_id": "invi_1kmn0aExample",
"available_for_sale": true
}
When the buyer picks XL in sand, create the order with that variant. Flint copies the name, price, SKU, and tax settings from the catalog onto the line, so your code never sends a price:
curl -X POST https://api.withflintpay.com/v1/orders \
-H "Authorization: Bearer YOUR_API_KEY" \
-H "Idempotency-Key: order-tee-1042" \
-H "Content-Type: application/json" \
-d '{
"line_items": [{ "variant_id": "var_1kmn0aExample", "quantity": 1 }],
"inventory_routing_source": { "type": "fixed_location", "location_id": "loc_1kmn0aExample" }
}'
Paying the order holds that variant's stock, commits it when the payment succeeds, and releases it if the payment fails. If the sand XL is gone, payment fails with INVENTORY_UNAVAILABLE before the card is charged. Send the order to hosted checkout with a checkout session, or put a variant on a payment link. The size and color picker stays on your product page; the hosted pages sell the variant it chose.
Promotions target the product, so one rule covers every size and color, including ones you add later:
{
"name": "Tee week",
"display_name": "20% off the crew tee",
"redemption_type": "automatic",
"discount_class": "line_item",
"application_method": {
"type": "percent_off",
"percent_off": 20,
"discounted_item_rules": [
{ "attribute": "line_item.product_id", "operator": "eq", "values": ["prod_1kmn0aExample"] }
]
}
}
Choices that change the price without being separate stock, such as gift wrap, a monogram, or oat milk, are a modifier set attached to the product. Each choice has a price change, Flint enforces how many the buyer must pick, and the order line lists them. The catalog guide builds a full example with sizes, modifiers, and a bundle.
Who Flint is built for
US businesses selling online or in their own app, paid in USD, that want the catalog, stock, orders, checkout, promotions, and automatic US sales tax behind one API instead of a payment API plus the tables above. See Flint for online stores.
Stripe and Flint catalogs, side by side
| Stripe Products and Prices | Flint catalog |
|---|
| One shirt in 4 sizes and 3 colors | 12 Products and 12 Prices | 1 product, 2 options, 12 variants |
| Which combination a record is | Metadata and your mapping table | selected_options on the variant |
| Price per size | A Price per Product | unit_price_money per variant |
| SKU | Metadata | sku, unique across the catalog |
| Stock per size | Your database and webhooks | Held at payment, per variant and location |
| Discount one style | Coupon applies_to.products listing every combination | One rule on line_item.product_id or a category |
| Paid add-ons | Optional items, up to 10, as separate products | Modifier sets with price changes |
| Where the buyer picks a size | Your page, before you create the session | Your page, before you create the order |
Questions, answered
Does Stripe support product variants?
No. A Stripe Product has no variant, option, or attribute fields. Stripe's own guidance is to create a separate product for each size and color combination. Checkout sells the prices you pass in the session, so the buyer chooses a size on your page before Checkout opens.
Should I use one Stripe product with several prices for sizes?
Stripe recommends against it when the options are different things the buyer chooses between. Line items that share a product share its name and description, so the receipt and invoice don't say which size was bought. Stripe reserves multiple prices on one product for billing differences such as monthly versus annual or another currency.
How do I let buyers pick a size and color with Stripe Checkout?
Build the picker on your product page, map the chosen size and color to a Price ID in your own table, and create the Checkout Session with that price. A dropdown custom field can ask for a size, but it doesn't change the price or the product being sold.
Can Stripe track inventory for each variant?
No. Products have no stock field. You keep a count per combination in your database and update it from webhooks, holding stock while a Checkout Session is open.
Which payments API has product variants built in?
Flint's catalog has products with options, such as Size and Color, and variants, each with its own price, SKU, tax category, and optional stock tracking. An order line names a variant, and Flint copies its name and price onto the order and holds its stock when the order is paid. Card processing runs on Stripe underneath.
Is Flint a good alternative to Stripe for selling products with sizes and colors?
Yes, if you sell online or in an app to buyers paying in USD from a US business. Flint keeps the catalog, variants, stock, orders, and checkout behind one API, so you don't maintain a mapping table, a stock counter, or price fan-out scripts.
Sources
All sources accessed October 15, 2026.