GuidesAdd products

Add products

A product on Flightdeck is the catalog entry a shopper browses; a variant is the thing they actually buy (a SKU with a price). A product with no variant has nothing to sell, so this guide creates both: a product and a sellable variant.

A product’s status is set at create time — create it active to make it publicly visible (once the store is live), or draft to keep it hidden while you finish stocking it.

Prerequisites

  • A merchant key holding manage:own_catalog. Your pre-live build key already has it — see Get your API keys.
  • Money is integer cents. A $49.99 price is 4999.

The self-serve catalog write routes below (POST/PATCH under /v1/merchant/catalog/...) are live HTTP endpoints but are not yet part of the published OpenAPI document, so the typed SDK client does not cover them today — use curl (or a raw fetch) for writes. The read route /v1/merchant/catalog is in the SDK.

Create and stock

Create the product with its status

A product needs a slug (its stable public identity) and a name. status is set here, at create time, and accepts only draft or active. Create it draft to keep it hidden while you finish stocking it, or active to make it publicly visible once the store is live.

curl -X POST https://api.ruo.pro/v1/merchant/catalog/products \
  -H "Authorization: Bearer fdk_your_key_here" \
  -H "Content-Type: application/json" \
  -d '{
    "slug": "bpc-157",
    "name": "BPC-157",
    "description": "Research peptide.",
    "status": "draft"
  }'

A 201 returns { "product": { ... } } including the generated id. Catalog writes take no Idempotency-Key header — retrying a create is safe because it converges on 409 slug_exists (the slug is the public identity and must be unique).

Add a sellable variant

Give the product a variant with a sku and a non-negative integer price_cents. currency defaults to usd; label and weight_grams (a positive integer, for shipping) are optional.

curl -X POST https://api.ruo.pro/v1/merchant/catalog/products/{productId}/variants \
  -H "Authorization: Bearer fdk_your_key_here" \
  -H "Content-Type: application/json" \
  -d '{
    "sku": "NOVA-BPC-5MG",
    "label": "5mg vial",
    "price_cents": 4999,
    "currency": "USD",
    "weight_grams": 40
  }'

A 201 returns { "variant": { ... } }. The SKU is immutable once set — pick it deliberately. Like product creation, variant creation takes no Idempotency-Key; a retried create converges on 409 sku_exists.

Make the product visible

If you created the product active, it is already visible in the public catalog once the store is live — no further step is needed. If you created it draft, there is one thing to know:

⚠️

Product status is set only at create time. There is no self-serve merchant route that transitions a product from draft to active after the fact — the PATCH route updates content (name, description, and other content fields) but never status. To publish a product you created as a draft today, either recreate it with status: "active" (a fresh slug, or remove the draft first), or ask the platform operator to change its status. Plan to create products active when you intend them to sell.

You can PATCH name, description, and other content fields at any time. An empty PATCH (no updatable fields) is a 400, not a silent no-op.

Verify it in the catalog

Read your tenant’s full catalog with the merchant credential (this read route is in the SDK and includes draft and archived products):

curl https://api.ruo.pro/v1/merchant/catalog \
  -H "Authorization: Bearer fdk_your_key_here"

Once the store is live, the same product also shows on the public, unauthenticated catalog — which only ever returns active products for a live store:

curl https://api.ruo.pro/v1/tenants/nova-peptide/catalog/products/bpc-157

The public product carries its variants, price_cents per variant, and both image_asset_ref (the raw stored ref) and image_url (the resolved, fetchable URL, or null if unset — see Product images).

Subscription products

To make a product subscribe-and-save eligible, declare the schedule at create time. is_subscription: true requires a valid subscription_interval (day | week | month | year) and a positive subscription_interval_count; subscription_price_cents is optional. A half-specified schedule is refused (400) rather than persisted — the renewal engine could not bill it. See Subscriptions.