docs(commerce): add Orders, Customers, Inventory, Promotions, Store pages

Flesh out the Commerce service section under the 10-category services
taxonomy: five hand-authored reference pages for the per-org commerce
surface (api.hanzo.ai/v1/commerce/*) — order lifecycle (returns, exchanges,
edits), guest/registered customers + groups, multi-location inventory,
rule-based promotions, and store settings (regions, currencies, sales
channels, publishable keys). meta.json groups them into Products / Orders /
Payments / Marketing / Store sections; validated as JSON with every slug
resolving to a page.
This commit is contained in:
2026-07-02 01:46:07 -07:00
parent 17fd72e3ca
commit f4f8dac486
6 changed files with 630 additions and 0 deletions
@@ -0,0 +1,111 @@
---
title: Customers
description: Manage guest and registered customers, addresses, and customer groups for targeted pricing and promotions.
---
# Customers
The Customers API stores and organizes the people who buy from your store — both guest and registered — and groups them so you can target pricing, promotions, and communications.
**Base URL:** `https://api.hanzo.ai/v1/commerce/customers`
## Customer object
| Field | Type | Description |
|-------|------|-------------|
| `id` | string | Unique customer identifier (`cus_...`) |
| `email` | string | Contact email (unique per store) |
| `first_name` | string | Given name |
| `last_name` | string | Family name |
| `has_account` | boolean | `true` for registered customers, `false` for guests |
| `phone` | string | Optional phone number |
| `addresses` | array | Saved shipping and billing addresses |
| `groups` | array | Customer groups this customer belongs to |
| `metadata` | object | Arbitrary key/value data you attach |
## Guest vs. registered customers
A **guest** is created automatically at checkout from the email provided. A **registered** customer has an authenticated account (`has_account: true`) and can sign in to view order history and manage saved addresses. Promote a guest to registered by attaching an identity from [Hanzo IAM](/docs/services/iam).
```typescript
import { HanzoCommerce } from '@hanzo/commerce'
const commerce = new HanzoCommerce({ apiKey: process.env.HANZO_API_KEY })
const customer = await commerce.customers.create({
email: '[email protected]',
first_name: 'Ada',
last_name: 'Lovelace'
})
```
## Addresses
Customers can store multiple addresses; mark one shipping and one billing address as default.
```bash
curl -X POST https://api.hanzo.ai/v1/commerce/customers/cus_123/addresses \
-H "Authorization: Bearer $HANZO_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"address_1": "1 Market St",
"city": "San Francisco",
"province": "CA",
"postal_code": "94105",
"country_code": "us",
"is_default_shipping": true
}'
```
## Customer groups
Groups organize customers for use cases like wholesale tiers, VIP pricing, or region-specific offers. A [promotion](/docs/services/commerce/promotions) or price list can be scoped to one or more groups.
```typescript
// Create a group and add members
const vip = await commerce.customers.createGroup({ name: 'VIP' })
await commerce.customers.addToGroup(vip.id, {
customer_ids: ['cus_123', 'cus_456']
})
```
```bash
# List members of a group
curl "https://api.hanzo.ai/v1/commerce/customers?group_id=cusgroup_vip" \
-H "Authorization: Bearer $HANZO_API_KEY"
```
<Callout type="info">
Customer groups are the join key between customers and pricing rules. Assigning a group-scoped price list or promotion applies automatically to every current and future member — no per-customer configuration.
</Callout>
## Endpoints
| Method | Endpoint | Description |
|--------|----------|-------------|
| `GET` | `/customers` | List customers (filter by `group_id`, `email`, `has_account`) |
| `POST` | `/customers` | Create a customer |
| `GET` | `/customers/:id` | Retrieve a customer |
| `PATCH` | `/customers/:id` | Update a customer |
| `DELETE` | `/customers/:id` | Delete a customer |
| `POST` | `/customers/:id/addresses` | Add an address |
| `PATCH` | `/customers/:id/addresses/:aid` | Update an address |
| `DELETE` | `/customers/:id/addresses/:aid` | Remove an address |
| `POST` | `/customers/groups` | Create a customer group |
| `POST` | `/customers/groups/:gid/customers` | Add customers to a group |
| `DELETE` | `/customers/groups/:gid/customers/:id` | Remove a customer from a group |
## Next steps
<Cards>
<Card title="Promotions" href="/docs/services/commerce/promotions">
Target discounts at specific customer groups
</Card>
<Card title="Orders" href="/docs/services/commerce/orders">
View a customer's order history
</Card>
<Card title="IAM" href="/docs/services/iam">
Back registered customers with Hanzo identities
</Card>
</Cards>
@@ -0,0 +1,124 @@
---
title: Inventory
description: Track stock across locations with inventory items, levels, reservations, and availability checks.
---
# Inventory
The Inventory API tracks the physical stock behind your catalog: how much of each item you have, where it lives, and how much is reserved for in-flight orders. It keeps *available* quantity accurate so you never oversell.
**Base URL:** `https://api.hanzo.ai/v1/commerce/inventory`
## Model
```
InventoryItem ──< InventoryLevel >── StockLocation
│ │
│ └── stocked / reserved / available
└──< Reservation (holds quantity at a location for an order)
```
- **Inventory item** — a stock-kept unit, typically linked to a product variant.
- **Stock location** — a warehouse, store, or fulfillment center.
- **Inventory level** — the quantity of one item at one location, split into `stocked`, `reserved`, and computed `available` (`stocked − reserved`).
- **Reservation** — a hold placed on `available` quantity when an order is placed, released or converted on fulfillment.
## Inventory item object
| Field | Type | Description |
|-------|------|-------------|
| `id` | string | Inventory item id (`iitem_...`) |
| `sku` | string | Stock keeping unit |
| `requires_shipping` | boolean | Whether the item ships physically |
| `levels` | array | Per-location stock levels |
| `reserved_quantity` | integer | Total reserved across all locations |
| `stocked_quantity` | integer | Total on hand across all locations |
## Manage stock levels
```typescript
import { HanzoCommerce } from '@hanzo/commerce'
const commerce = new HanzoCommerce({ apiKey: process.env.HANZO_API_KEY })
// Set the on-hand quantity for an item at a location
await commerce.inventory.setLevel({
inventory_item_id: 'iitem_1',
location_id: 'sloc_warehouse_sf',
stocked_quantity: 500
})
```
```bash
# Read levels for an item across every location
curl https://api.hanzo.ai/v1/commerce/inventory/iitem_1/levels \
-H "Authorization: Bearer $HANZO_API_KEY"
```
## Reservations
Reservations hold `available` quantity so concurrent checkouts can't oversell. Create one when an order is placed; release it on cancellation or convert it on fulfillment.
```typescript
const reservation = await commerce.inventory.reserve({
inventory_item_id: 'iitem_1',
location_id: 'sloc_warehouse_sf',
quantity: 2,
order_id: 'order_123'
})
```
## Check availability
```bash
curl -X POST https://api.hanzo.ai/v1/commerce/inventory/availability \
-H "Authorization: Bearer $HANZO_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"inventory_item_id": "iitem_1",
"location_ids": ["sloc_warehouse_sf", "sloc_store_nyc"],
"quantity": 2
}'
# → { "available": true, "available_quantity": 498 }
```
<Callout type="info">
`available = stocked − reserved`. Always check availability against the specific locations a [sales channel](/docs/services/commerce/storefront) can fulfill from — an item can be in stock globally but unavailable to a given storefront.
</Callout>
## Stock locations
```typescript
const sf = await commerce.inventory.createLocation({
name: 'San Francisco Warehouse',
address: { city: 'San Francisco', country_code: 'us' }
})
```
## Endpoints
| Method | Endpoint | Description |
|--------|----------|-------------|
| `GET` | `/inventory` | List inventory items |
| `POST` | `/inventory` | Create an inventory item |
| `GET` | `/inventory/:id/levels` | List levels for an item |
| `POST` | `/inventory/levels` | Set/update a level at a location |
| `POST` | `/inventory/availability` | Check availability across locations |
| `POST` | `/inventory/reservations` | Reserve quantity for an order |
| `DELETE` | `/inventory/reservations/:rid` | Release a reservation |
| `GET` | `/inventory/locations` | List stock locations |
| `POST` | `/inventory/locations` | Create a stock location |
## Next steps
<Cards>
<Card title="Products" href="/docs/services/commerce/products">
Link inventory items to product variants
</Card>
<Card title="Orders" href="/docs/services/commerce/orders">
Reservations are created and released along the order lifecycle
</Card>
<Card title="Store settings" href="/docs/services/commerce/storefront">
Control which locations each sales channel fulfills from
</Card>
</Cards>
@@ -6,10 +6,18 @@
"quickstart",
"---Products---",
"products",
"inventory",
"subscriptions",
"---Orders---",
"orders",
"customers",
"---Payments---",
"payments",
"webhooks",
"---Marketing---",
"promotions",
"---Store---",
"storefront",
"---Reference---",
"api",
"configuration"
@@ -0,0 +1,164 @@
---
title: Orders
description: Manage the full order lifecycle — orders, draft orders, fulfillments, returns, exchanges, and edits.
---
# Orders
The Orders API manages the complete lifecycle of a purchase: from cart capture through fulfillment, and every post-purchase change — returns, exchanges, claims, and edits — with a versioned timeline so every mutation is auditable.
**Base URL:** `https://api.hanzo.ai/v1/commerce/orders`
## Order lifecycle
```
draft ──▶ pending ──▶ completed
│
├──▶ canceled
│
├──▶ return ──▶ refunded
├──▶ exchange ──▶ new fulfillment
└──▶ edit ──▶ new version
```
An order moves through **pending** once payment is authorized, and **completed** once every fulfillment is delivered. Post-purchase operations (returns, exchanges, edits) create *order changes* that append a new version to the order's timeline rather than mutating history.
## Order object
| Field | Type | Description |
|-------|------|-------------|
| `id` | string | Unique order identifier (`order_...`) |
| `status` | enum | `draft`, `pending`, `completed`, `canceled` |
| `customer_id` | string | Customer who placed the order |
| `email` | string | Contact email (guest or registered) |
| `currency` | string | ISO 4217 currency code |
| `items` | array | Line items with quantity, unit price, and adjustments |
| `shipping_methods` | array | Selected shipping options with tax and promotion adjustments |
| `payment_status` | enum | `not_paid`, `authorized`, `captured`, `partially_refunded`, `refunded` |
| `fulfillment_status` | enum | `not_fulfilled`, `partially_fulfilled`, `fulfilled`, `delivered` |
| `total` | number | Order grand total after tax and discounts |
| `version` | integer | Current timeline version |
## List and retrieve orders
```bash
# List orders (paginated, filterable by status/customer/date)
curl "https://api.hanzo.ai/v1/commerce/orders?status=pending&limit=20" \
-H "Authorization: Bearer $HANZO_API_KEY"
# Retrieve a single order
curl https://api.hanzo.ai/v1/commerce/orders/order_123 \
-H "Authorization: Bearer $HANZO_API_KEY"
```
```typescript
import { HanzoCommerce } from '@hanzo/commerce'
const commerce = new HanzoCommerce({ apiKey: process.env.HANZO_API_KEY })
const orders = await commerce.orders.list({ status: 'pending', limit: 20 })
const order = await commerce.orders.retrieve('order_123')
```
## Draft orders
Draft orders let a merchant build an order on behalf of a customer — quotes, phone sales, or B2B — then convert it to a live order when the customer confirms.
```typescript
const draft = await commerce.orders.createDraft({
email: '[email protected]',
currency: 'USD',
items: [{ variant_id: 'variant_1', quantity: 2, unit_price: 2500 }],
shipping_methods: [{ option_id: 'so_standard' }]
})
// Convert the draft to a pending order once the customer confirms
const order = await commerce.orders.completeDraft(draft.id)
```
## Fulfillments
Create a fulfillment to ship all or part of an order. Each fulfillment tracks the items, shipping location, and delivery state.
```bash
curl -X POST https://api.hanzo.ai/v1/commerce/orders/order_123/fulfillments \
-H "Authorization: Bearer $HANZO_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"location_id": "sloc_warehouse_sf",
"items": [{ "item_id": "item_1", "quantity": 1 }]
}'
```
Mark a fulfillment as shipped or delivered to advance the order's `fulfillment_status`:
```bash
curl -X POST https://api.hanzo.ai/v1/commerce/orders/order_123/fulfillments/ful_1/ship \
-H "Authorization: Bearer $HANZO_API_KEY" \
-H "Content-Type: application/json" \
-d '{ "tracking_numbers": ["1Z999AA10123456784"] }'
```
## Returns, exchanges, and claims
Post-purchase changes are modeled as **order changes**. Each creates a new order version so the timeline is fully auditable.
| Operation | Endpoint | Effect |
|-----------|----------|--------|
| Return | `POST /orders/:id/returns` | Customer sends items back; issues a refund on receipt |
| Exchange | `POST /orders/:id/exchanges` | Return items and ship replacements in one flow |
| Claim | `POST /orders/:id/claims` | Resolve damaged/incorrect items (refund or replace) |
| Edit | `POST /orders/:id/edits` | Add, remove, or re-price items after purchase |
```typescript
// Request a return with a refund on receipt
const ret = await commerce.orders.createReturn('order_123', {
items: [{ item_id: 'item_1', quantity: 1, reason: 'wrong_size' }],
refund_on_receive: true
})
```
<Callout type="info">
Every return, exchange, edit, and claim appends a new `version` to the order. Fetch `GET /orders/:id/changes` to render the full timeline of who changed what and when.
</Callout>
## Refunds
```bash
curl -X POST https://api.hanzo.ai/v1/commerce/orders/order_123/refund \
-H "Authorization: Bearer $HANZO_API_KEY" \
-H "Content-Type: application/json" \
-d '{ "amount": 2500, "reason": "return_accepted" }'
```
## Endpoints
| Method | Endpoint | Description |
|--------|----------|-------------|
| `GET` | `/orders` | List orders |
| `GET` | `/orders/:id` | Retrieve an order |
| `POST` | `/orders/draft` | Create a draft order |
| `POST` | `/orders/:id/complete` | Complete a draft into a pending order |
| `POST` | `/orders/:id/fulfillments` | Create a fulfillment |
| `POST` | `/orders/:id/fulfillments/:fid/ship` | Mark shipped |
| `POST` | `/orders/:id/returns` | Request a return |
| `POST` | `/orders/:id/exchanges` | Create an exchange |
| `POST` | `/orders/:id/claims` | File a claim |
| `POST` | `/orders/:id/edits` | Edit order items |
| `POST` | `/orders/:id/refund` | Refund all or part of an order |
| `POST` | `/orders/:id/cancel` | Cancel an order |
| `GET` | `/orders/:id/changes` | Retrieve the versioned change timeline |
## Next steps
<Cards>
<Card title="Customers" href="/docs/services/commerce/customers">
Attach orders to customers and customer groups
</Card>
<Card title="Inventory" href="/docs/services/commerce/inventory">
Reserve and deduct stock as orders are placed and fulfilled
</Card>
<Card title="Payments" href="/docs/services/commerce/payments">
Authorize, capture, and refund order payments
</Card>
</Cards>
@@ -0,0 +1,120 @@
---
title: Promotions
description: Build discounts with flexible rules, promotion codes, and budgeted campaigns.
---
# Promotions
The Promotions API discounts an amount or percentage off a cart's items, its shipping, or the entire order — governed by rules that decide *when* the discount applies and grouped into *campaigns* that cap spend and set a schedule.
**Base URL:** `https://api.hanzo.ai/v1/commerce/promotions`
## Promotion types
| Type | Behavior |
|------|----------|
| `standard` | Applies a discount when the cart matches the rules |
| `buyget` | "Buy X, get Y" — discounts target items when required items are present |
## Application method
Every promotion has an **application method** describing the discount:
| Field | Values | Description |
|-------|--------|-------------|
| `type` | `fixed`, `percentage` | Fixed amount off, or a percentage |
| `target_type` | `items`, `shipping_methods`, `order` | What the discount reduces |
| `value` | number | Amount (minor units) or percent |
| `allocation` | `each`, `across` | Apply per matching item, or spread across the target |
```typescript
import { HanzoCommerce } from '@hanzo/commerce'
const commerce = new HanzoCommerce({ apiKey: process.env.HANZO_API_KEY })
const promo = await commerce.promotions.create({
code: 'WELCOME10',
type: 'standard',
application_method: {
type: 'percentage',
target_type: 'items',
value: 10,
allocation: 'each'
}
})
```
## Flexible rules
Rules restrict when a promotion is applied by matching attributes of the cart, its items, or the customer. Combine multiple rules — all must pass.
```json
{
"rules": [
{ "attribute": "customer.groups.id", "operator": "in", "values": ["cusgroup_vip"] },
{ "attribute": "items.product.collection", "operator": "eq", "values": ["col_outerwear"] },
{ "attribute": "currency_code", "operator": "eq", "values": ["usd"] }
]
}
```
Supported operators: `eq`, `ne`, `in`, `not_in`, `gt`, `gte`, `lt`, `lte`.
<Callout type="info">
Target a promotion at a [customer group](/docs/services/commerce/customers) with a `customer.groups.id` rule to run tiered or member-only pricing without touching individual customers.
</Callout>
## Promotion codes
A promotion can be automatic (applies with no code) or gated behind a `code` the shopper enters. Generate many unique codes for a single promotion when you need per-recipient tracking.
```bash
# Apply a code to a cart
curl -X POST https://api.hanzo.ai/v1/commerce/carts/cart_1/promotions \
-H "Authorization: Bearer $HANZO_API_KEY" \
-H "Content-Type: application/json" \
-d '{ "code": "WELCOME10" }'
```
## Campaigns
A campaign groups promotions under a shared schedule and budget. When the budget is spent or the window closes, its promotions stop applying automatically.
```typescript
const campaign = await commerce.promotions.createCampaign({
name: 'Spring Sale',
starts_at: '2026-03-01T00:00:00Z',
ends_at: '2026-03-31T23:59:59Z',
budget: { type: 'spend', limit: 100000, currency: 'USD' },
promotion_ids: [promo.id]
})
```
Budget types: `spend` (cap total discount value) or `usage` (cap number of redemptions).
## Endpoints
| Method | Endpoint | Description |
|--------|----------|-------------|
| `GET` | `/promotions` | List promotions |
| `POST` | `/promotions` | Create a promotion |
| `GET` | `/promotions/:id` | Retrieve a promotion |
| `PATCH` | `/promotions/:id` | Update a promotion or its rules |
| `DELETE` | `/promotions/:id` | Delete a promotion |
| `POST` | `/promotions/:id/rules` | Add rules |
| `POST` | `/promotions/campaigns` | Create a campaign |
| `PATCH` | `/promotions/campaigns/:cid` | Update a campaign budget/schedule |
## Next steps
<Cards>
<Card title="Customers" href="/docs/services/commerce/customers">
Scope promotions to customer groups
</Card>
<Card title="Orders" href="/docs/services/commerce/orders">
Promotion adjustments are recorded on order line items
</Card>
<Card title="Products" href="/docs/services/commerce/products">
Match promotions against product attributes and collections
</Card>
</Cards>
@@ -0,0 +1,103 @@
---
title: Store settings
description: Configure your store — regions, currencies, sales channels, and publishable keys that power each storefront.
---
# Store settings
Store settings define the commercial context every cart and order runs in: which **regions** you sell to, which **currencies** you price in, and which **sales channels** (storefronts) expose which products. One backend can drive many storefronts.
**Base URL:** `https://api.hanzo.ai/v1/commerce/store`
## Regions
A region groups countries that share a currency, tax behavior, and set of payment and fulfillment options. Carts and orders always belong to a region, which fixes their currency and tax rules.
```typescript
import { HanzoCommerce } from '@hanzo/commerce'
const commerce = new HanzoCommerce({ apiKey: process.env.HANZO_API_KEY })
const region = await commerce.store.createRegion({
name: 'North America',
currency_code: 'usd',
countries: ['us', 'ca'],
automatic_taxes: true
})
```
| Field | Type | Description |
|-------|------|-------------|
| `id` | string | Region id (`reg_...`) |
| `currency_code` | string | ISO 4217 currency for all prices in the region |
| `countries` | array | ISO country codes served |
| `automatic_taxes` | boolean | Calculate tax at checkout |
| `payment_providers` | array | Enabled payment providers |
## Currencies
Enable the currencies your store prices in. Each region pins exactly one currency; products carry a price per enabled currency.
```bash
curl -X POST https://api.hanzo.ai/v1/commerce/store/currencies \
-H "Authorization: Bearer $HANZO_API_KEY" \
-H "Content-Type: application/json" \
-d '{ "codes": ["usd", "eur", "gbp"], "default": "usd" }'
```
## Sales channels
A sales channel is a storefront — web, mobile, marketplace, or point of sale. Products are published per channel, and each channel fulfills from a specific set of [stock locations](/docs/services/commerce/inventory), so a product can be sold on one channel and hidden on another.
```typescript
const web = await commerce.store.createSalesChannel({ name: 'Web Store' })
// Publish products to the channel
await commerce.store.publishProducts(web.id, {
product_ids: ['prod_1', 'prod_2']
})
```
## Publishable API keys
A publishable key is a client-safe key that scopes storefront requests to one or more sales channels. Use it in browser and mobile clients; use a secret `Bearer` key only on your server.
```bash
curl -X POST https://api.hanzo.ai/v1/commerce/store/publishable-keys \
-H "Authorization: Bearer $HANZO_API_KEY" \
-H "Content-Type: application/json" \
-d '{ "title": "Web Store", "sales_channel_ids": ["sc_web"] }'
# → { "id": "pk_...", "token": "pk_live_..." }
```
<Callout type="info">
Never ship a secret key to a browser. Publishable keys are scoped to sales channels and safe to embed in client code; secret keys authorize the full admin surface and belong only on your server or in [Hanzo KMS](/docs/services/kms).
</Callout>
## Endpoints
| Method | Endpoint | Description |
|--------|----------|-------------|
| `GET` | `/store` | Retrieve store configuration |
| `PATCH` | `/store` | Update store settings |
| `GET` | `/store/regions` | List regions |
| `POST` | `/store/regions` | Create a region |
| `POST` | `/store/currencies` | Enable currencies |
| `GET` | `/store/sales-channels` | List sales channels |
| `POST` | `/store/sales-channels` | Create a sales channel |
| `POST` | `/store/sales-channels/:id/products` | Publish products to a channel |
| `POST` | `/store/publishable-keys` | Create a publishable key |
## Next steps
<Cards>
<Card title="Products" href="/docs/services/commerce/products">
Publish products to sales channels and price per currency
</Card>
<Card title="Inventory" href="/docs/services/commerce/inventory">
Map sales channels to fulfillment locations
</Card>
<Card title="KMS" href="/docs/services/kms">
Store secret commerce keys securely
</Card>
</Cards>