Skip to main content
POST

Authorizations

Authorization
string
header
required

Your secret API key as a Bearer token: Authorization: Bearer vxn_sk_.... Keys are created and scoped in the dashboard - see Authentication.

Headers

Idempotency-Key
string
required

Required on every mutating POST. 24h byte-replay on the same key + body; 409 idempotency_key_conflict on body mismatch. See Idempotency.

Required string length: 8 - 255

Body

application/json

Create a manual catalog entry. POS-sourced entries cannot be created via API - they arrive through the location's POS sync. Server-owned fields (pos_ref, source) are rejected with 422 parameter_unknown.

location_id
string
required

The location catalog to add to. Must belong to your organization.

Pattern: ^loc_[A-Za-z0-9]+$
Example:

"loc_01HW9R4P2ZK7N8MTX1RVLOCS01"

name
required

Display name - plain string or per-language object, the same localization pattern as Card.name. POS-synced entries arrive with the till's single-language name; supply per-language names on manual/API entries when your audience is multilingual (border stores, tourist areas). Notifications and future product-facing surfaces resolve the name in the customer's language.

Required string length: 1 - 200
Example:

"Flat White"

sku
string | null

Strongly recommended - the correlation key line items resolve on. Unique per location (409 sku_already_exists on collision).

Required string length: 1 - 64
Example:

"FW-12"

category
string | null

Free-form category.

Required string length: 1 - 100
tags
string[]

Your own filing labels. They join the product name in the search haystack, so a tag makes the product findable even when the search text appears nowhere in its name. Omit for none.

price_minor
integer | null

Reference price in minor units. Maximum 100,000,000,000.

Required range: 0 <= x <= 100000000000

Response

Product created.

A catalog entry for correlating transaction line items to real products. Catalogs are per-location (each location can run a different POS with a different menu), unified under the organization: list org-wide, filter by location_id.

This is a loyalty correlation catalog, not an inventory system - no variants, modifiers, or stock levels.

id
string
required

Product identifier, prd_ prefix. Stable forever - line items resolve to it.

Pattern: ^prd_[A-Za-z0-9]+$
Example:

"prd_01HW9R7T2CD8N8MTX1RVPRDFW1"

object
string
required

Always "product".

Allowed value: "product"
location_id
string
required

The location this catalog entry belongs to. The same drink sold at two locations is two Product records (each mapped to its own POS).

Pattern: ^loc_[A-Za-z0-9]+$
Example:

"loc_01HW9R4P2ZK7N8MTX1RVLOCS01"

name
required

Display name - plain string or per-language object, the same localization pattern as Card.name. POS-synced entries arrive with the till's single-language name; supply per-language names on manual/API entries when your audience is multilingual (border stores, tourist areas). Notifications and future product-facing surfaces resolve the name in the customer's language.

Required string length: 1 - 200
Example:

"Flat White"

source
string
required

Where this catalog entry originated. Known values: pos (synced from the location's connected POS), manual (dashboard), api (created via POST /v1/products). Server-stamped - treat as an open set.

Example:

"pos"

is_active
boolean
required

False = archived (delisted from the POS or manually retired). Archived products stay resolvable on historical line items - there is no hard delete.

Example:

true

created_at
integer
required

Unix seconds.

Example:

1750000000

sku
string | null

Your stable key, unique per location (two locations may both use FW-12). This is what transaction line items correlate on.

Required string length: 1 - 64
Example:

"FW-12"

category
string | null

Free-form category for basket analytics.

Required string length: 1 - 100
Example:

"drinks"

tags
string[]

Merchant-owned filing aid, and part of the search haystack alongside the name. NEVER populated by POS sync: no POS system sends tags, so a source: "pos" entry arrives untagged. You can tag one yourself with PATCH /v1/products/{id} (tags are an overlay field, unlike pos_ref and source, which sync owns), and it becomes findable by that tag immediately. Sending [] clears them and removes them from the search haystack.

Example:
price_minor
integer | null

Reference price in minor units. Informational - the POS bill is authoritative at transaction time.

Required range: x >= 0
Example:

350

pos_ref
object | null

The POS mapping for this location's adapter. Read-only, owned by the sync - never editable via API. Null for manual/API entries. The POS vendor name is never surfaced.

Example:

null

updated_at
integer

Unix seconds of last change.

Example:

1750000000