The atomic-create principle
The Arcus Products API accepts the complete nested resource graph in a single POST. You express what you want; the API creates every child in one atomic transaction. If any child fails, the entire request rolls back — no orphan products, no partial state.product_type discriminator
Every product has aproduct_type that controls which children are valid:
Only
title and product_type are required; a missing one returns 400 missing_required_field with the name in param. The key needs the products:write scope. A few rules apply to every type:
list_price,costandlast_purchase_pricecannot be negative. A free item with a price of 0 is fine.part_numbermust be unique among your active products; a repeat returns409.- A
serviceproduct never tracks inventory, whatever you send fortrack_inventory.
Full example: variant parent with kit variants
A “Riser Kit” with two kit variants — Dome and Flat — each with their own component assemblies, three qty-break pricing tiers, and a vendor assignment. One POST call.pricing and vendors hydrated inline. For a kit or box, the components[] you send are created in the same transaction, but the create response does not echo them back; read them with GET /v1/products/{id}/components. Variant components are returned inside each variant, as below.
Qty-break pricing with pricing_level_id
Each pricing[] element defines one row in a tiered price book:
An active tier is unique for its combination of product, variant, pricing level or account, and
qty_break. Sending a second one returns 422 duplicate_pricing_policy_for_scope, with param: "qty_break" and the id of the existing tier in conflicting_policy_id; edit that tier or choose a different level, account or quantity. A pricing level or account that is not in your entity is refused.
All money fields (list_price, sell_price, adjustment) are returned as JavaScript numbers (float), not strings.
Idempotency
IncludeIdempotency-Key: <your-unique-key> on every create call. Replaying the same key within 24 hours returns the original response without re-creating resources.
Standalone kit (no variants)
Error handling
Validation errors return the exactparam path so you know which field in which child failed:
param path is prefixed with the child it belongs to, such as variants[0].pricing[2] or vendors[0].
Adding pricing tiers to an existing product
UsePOST /v1/products/{id}/pricing to add tiers incrementally after creation:
products:write and returns 201 with the new tier in data: its id, product_id, pricing_level_id, qty_break, pricing_type, list_price, sell_price, is_default, and demoted_default_policy_id (null unless the new tier took over as default). A product that is not in your entity returns 404. Send variant_id to price one variant of a variant parent rather than the parent.
