Skip to navigation

Products

The thing you sell.

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 the fluveo-billing sidecar (ADR 0019); create/retrieve/list promoted 2026-07-13, update and delete 2026-09-15. The Stripe-Version header 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.

curl https://api.devfluveo.com/v1/products \
-u "$FLUVEO_SECRET_KEY:" \
-d name="Running shoe" \
-d "images[0]"=file_4b23e707fcc06465594f9c71

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.