Product schema
| Field | Type | Required | Description |
|---|---|---|---|
name | string | Yes | Product name |
description | string | No | Product description (whitespace-trimmed; blank becomes null) |
sku | string | No | Unique product identifier for business-facing lookups |
type | enum | Yes | point_in_time or period_of_time |
subtype | enum | Yes | quantity, credits, or feature (must be compatible with type) |
unit | object | No | Unit labels — { singular: "API call", plural: "API calls" } |
tax_category | string | No | Zenskar tax code for tax resolution |
status | enum | No | active (default), inactive, or archived |
slug | string | No | Optional reference to an external system |
custom_attributes | object | No | Arbitrary key-value metadata |
Type-subtype compatibility
| Product type | Allowed subtypes |
|---|---|
point_in_time | quantity, credits |
period_of_time | quantity, feature |
Product statuses
| Status | Description |
|---|---|
active | Available for use in new contracts |
inactive | Not available for new contracts; existing contracts unaffected |
archived | End-of-life; can’t be set on creation |
API endpoints
| Method | Path | Description | Permissions |
|---|---|---|---|
POST | /v3/products | Create a new product | can_read_product, can_create_product |
GET | /v3/products | List products (paginated, filterable) | can_read_product |
GET | /v3/products/pricings | Search products with nested pricing presets | can_read_product, can_read_product_pricing |
GET | /v3/products/{product_id} | Get a product by ID | can_read_product |
PATCH | /v3/products/{product_id} | Partially update a product | can_read_product, can_update_product |
DELETE | /v3/products/{product_id} | Soft-delete a product and its pricing presets | can_read_product, can_delete_product |
Business validation rules
Enforced byvalidate_product_catalog on create and update.
| # | Rule | Applies to | Error code | Description |
|---|---|---|---|---|
| 1 | Status not archived on create | Create | PRODUCT_CREATED_AS_ARCHIVED | New products can’t be created with archived status; valid initial statuses are active or inactive |
| 2 | Type-subtype compatibility | Create | PRODUCT_TYPE_SUBTYPE_INCOMPATIBLE | Subtype must be allowed for the given type |
| 3 | Product name unique per org | Create | PRODUCT_NAME_DUPLICATE | Currently disabled |
| 4 | SKU unique per org | Create & update | PRODUCT_SKU_DUPLICATE | On update, the product may keep its own existing SKU; skipped if SKU isn’t provided |
| 5 | Slug unique per org | Create & update | PRODUCT_SLUG_DUPLICATE | On update, the product may keep its own existing slug; skipped if slug isn’t provided |
| 6 | Type immutable with pricing | Update | PRODUCT_TYPE_CHANGE_WITH_PRICING | Can’t change type once pricing presets exist |
| 7 | Can’t deactivate with contracts | Update | PRODUCT_DEACTIVATE_WITH_CONTRACTS | Can’t set status to inactive if the product is used in any active contract |
Delete constraints
Separate fromvalidate_product_catalog.
| Rule | Error | Description |
|---|---|---|
| Can’t delete with contracts | Cannot delete product as it is being used in {n} contracts | A product can’t be deleted while referenced by any contract |
Repercussions: product update
| Change | Impact on contracts | Impact on invoices |
|---|---|---|
| Name / description / SKU / slug change | Metadata-only; contracts reference the product by ID and reflect the change on next read | Future invoices show the updated name; finalized invoices unaffected |
Status to inactive | Blocked if any active contract references the product; finalized contracts continue to function | No impact on existing invoices; product becomes unavailable for new line items |
Status to archived | Product not available for any new usage | No impact on finalized invoices |
| Type changed | Blocked if pricing presets exist; if none exist, only affects how future pricing can be configured | No impact — pricing must be recreated after a type change |
| Subtype changed | Only validated on create; changing on update may cause inconsistency with existing pricing | Indirect — may cause pricing computation mismatches |
| Unit changed | Contracts see updated unit labels, unless a pricing preset overrides the unit | Future invoices reflect the new label; finalized invoices unaffected |
| Custom attributes changed | Informational only | No impact |
Repercussions: product deletion
| Impact | Description |
|---|---|
| Contracts | Deletion blocked if any contracts reference the product |
| Pricing presets | All pricing presets belonging to the product are soft-deleted with it |
| Invoices | Finalized invoices retain historical data; soft-deleted product and pricing remain in the database for audit purposes |
Not yet available
- Product groups and bundles.
- Editing pricing on an already-created product.
- (Placeholder: any additional reference items not yet documented.)