Products
Status:
contracted_test. In the machine-checked test-mode contract (openapi.subset.json) and the API Reference: create, retrieve, list, update and delete. Served by thefluveo-billingsidecar (ADR 0019); create/retrieve/list promoted 2026-07-13, update and delete 2026-09-15. TheStripe-Versionheader is optional. See API coverage.
A Product describes something you sell — a plan, a SKU, a service. A product on its own has no price; you attach one or more prices to it. Products are the catalog root for invoices and subscriptions.
Served operations: create, retrieve, list, update and delete — all five contracted in test mode. Search is not served.
Create and update both take name, description, active, metadata,
statement_descriptor, unit_label, url, shippable, tax_code, type,
package_dimensions, images and marketing_features. On update, an
explicitly empty value clears a nullable field and an omitted one leaves it
alone — the difference between description= and not sending description at
all.
Images
images takes up to 8 entries, each either a file_… id or an absolute
https:// URL, and the first is the one dashboards and checkout surfaces draw.
What you store is the id; what a read returns is a URL composed from it. A URL stored at write time dies when the environment changes — a product created in a sandbox would carry a dead link in production — and a presigned storage URL expires in minutes, so it was never a durable value.
The Fluveo dashboard uploads images for you — JPEG, PNG or WEBP up to 2 MB — and
stores the id on the product. There is no public upload for this purpose yet:
POST /v1/files proxies to the file service’s dispute-evidence door, which
accepts dispute_evidence and nothing else. From the API you pass an absolute
https:// URL, which is stored and returned verbatim.
images= clears the list; omitting it leaves it alone.
A product and its first price can be created in one call: default_price_data
mints the price alongside the product and points default_price at it, so
there is no window where the product exists and the price does not. On update,
default_price takes the id of an existing price of that product; a price
belonging to another product, or an archived one, is refused.
Delete removes a product that has no prices. A product any price references is
refused — “a product with prices cannot be deleted; archive it instead” —
because deleting it would leave those prices pointing at nothing. Archiving is
active=false, and it is what you want in almost every case: archived products
stay readable and keep their prices. A delete emits product.deleted.
Lists honor limit (1–100, default 10), starting_after, ending_before,
active, created and ids[], ordered newest-first. metadata is returned on
every read, and the product object carries the full Stripe key set — fields
Fluveo does not populate yet are null rather than absent, so the shape does
not change as they land.
expand, id and tax_details are recognised and rejected by name rather
than silently ignored, as is any unknown parameter.