Products

Create products in the dashboard or via API — they live directly on your spuke account.

Products

A Product describes something you sell — a shoe, a subscription tier, a service. Products in spuke are created directly on your spuke account — either via the dashboard (Dashboard → Products → New) or via the public API described below.

Fields

Field Description
name Public name shown on receipts, invoices and checkout.
description Long description.
images[] Up to 8 image URLs. First image is used as the thumbnail.
tax_code Optional spuke tax code (txcd_…).
unit_label e.g. "seat", "month".
statement_descriptor What appears on the buyer's card statement (≤ 22 chars).
url Link back to your product page.
shippable Physical goods = true.
package_dimensions Length / width / height (cm) and weight (g).
metadata Free-form key/value.
tags[] spuke-internal — used for catalog auto-grouping.
default_price The Price object linked as the default.

Minimum price

Every unit_amount must be ≥ 250 minor units (2.50) in the price's currency. Smaller amounts don't cover PSP + spuke fees.


API — Create a product

POST https://api.spuke.com/v1/products

Authenticate with a secret API key (sk_live_… / sk_test_…) — scope products:write. Pass an optional Idempotency-Key header to make retries safe.

curl https://api.spuke.com/v1/products \
  -H "Authorization: Bearer sk_live_..." \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: prod-launch-sneaker-v1" \
  -d '{
    "name": "Runner 01",
    "description": "Lightweight everyday shoe",
    "images": ["https://cdn.example.com/runner-01.jpg"],
    "shippable": true,
    "metadata": { "sku": "RUN-01" },
    "tags": ["shoes", "new"],
    "price": {
      "currency": "eur",
      "unit_amount": 8900
    }
  }'

Pass a price object to create the Product and its default Price in one call. Omit it if you want to add prices later.

One-time vs recurring prices

A Product itself is just the catalog item. Whether it is charged once or repeatedly is controlled by the attached Price:

  • One-time paid product: send price.currency + price.unit_amount only.
  • Recurring paid product: send the same price fields plus price.recurring.

Recurring price fields:

Field Description
price.recurring.interval Required for recurring prices. Allowed: day, week, month, year.
price.recurring.interval_count Optional. Defaults to 1. Example: 3 + month = every 3 months.
price.recurring.trial_period_days Optional trial length in days.
price.recurring.usage_type Optional: licensed or metered.

Example — monthly recurring product:

curl https://api.spuke.com/v1/products \
  -H "Authorization: Bearer sk_live_..." \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: prod-pro-plan-monthly-v1" \
  -d '{
    "name": "Pro Plan",
    "description": "Monthly access to Pro features",
    "metadata": { "sku": "PRO-MONTHLY" },
    "tags": ["subscription", "pro"],
    "price": {
      "currency": "eur",
      "unit_amount": 2900,
      "recurring": {
        "interval": "month",
        "interval_count": 1,
        "trial_period_days": 14
      }
    }
  }'

The response will include a recurring Price with type: "recurring" and the recurring interval fields.

Response

{
  "id": "prod_ABC123",
  "object": "product",
  "name": "Runner 01",
  "active": true,
  "default_price": "price_XYZ789",
  "prices": [{ "id": "price_XYZ789", "currency": "eur", "unit_amount": 8900, ... }],
  "metadata": { "sku": "RUN-01" },
  "tags": ["shoes", "new"]
}

The returned prod_… and price_… IDs are what you use everywhere else — catalog management and invoice product selection. Recurring prices define the billing terms, but automatic subscription billing requires a recurring-billing flow.


API — Other product operations

Method Path Description
GET /v1/products List products (params: limit, starting_after). Scope products:read.
GET /v1/products/{id} Retrieve a product with all its prices.
POST /v1/products/{id} Update fields (name, description, images, active, metadata, tags, …).
DELETE /v1/products/{id} Archive the product and all its prices (active=false).
POST /v1/products/{id}/default_price Body { "price": "price_…" } — change the default price.

API — Prices on a product

Method Path Description
POST /v1/products/{id}/prices Create a new Price on this product. Fields: currency, unit_amount, nickname, recurring, tax_behavior, lookup_key, set_as_default, metadata.
GET /v1/products/{id}/prices List all prices on this product.

Prices are immutable except for active, nickname, tax_behavior, lookup_key, metadata. To change amount or currency, create a new Price and mark the old one inactive.

curl https://api.spuke.com/v1/products/prod_ABC123/prices \
  -H "Authorization: Bearer sk_live_..." \
  -H "Content-Type: application/json" \
  -d '{
    "currency": "eur",
    "unit_amount": 9900,
    "nickname": "2026 launch price",
    "set_as_default": true
  }'

Using a product

  • In an invoice: New invoice → Add from products → the item is locked to the product's spuke values.
  • In a Checkout Session: pass line_items[].price_id: "price_…" so receipts and analytics link back to the catalog entry.

Product ID vs Price ID

ID What it is When you use it
prod_… The container — name, description, images. Reporting, catalog management.
price_… The billable price — amount, currency, one-time or recurring. Every transaction and invoice line-item.

Rule of thumb: to charge someone you always need a price (or an ad-hoc amount), never just a product.

Idempotency

All POST endpoints (/v1/products, /v1/products/{id}, /v1/products/{id}/prices, /v1/products/{id}/default_price) accept Idempotency-Key. Same key + same body replays the original response; same key + different body returns 409 Conflict with type: "idempotency_error", code: "idempotency_key_reused". See Error reference.