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_amountonly. - 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.