Sparse Updates
Create a cost type, then rename it without touching anything else
// POST /public/v1/cost-types — create
{ "name": "Freight", "cost_per_unit": "5.00", "active": true }
// POST /public/v1/cost-types — update: only the name changes; cost_per_unit and active are kept
{ "id": "8f3c…", "name": "Inbound Freight" }
// clear the description, leave everything else as-is
{ "id": "8f3c…", "description": null }Every write endpoint is an upsert: one POST both creates and updates. Omit the
id to create a new record; include an existing id to update that record. The URL
and request shape are identical either way.
On an update you send only the fields you want to change:
- Omit a field → left unchanged.
- Send a field with a value → set to that value.
- Send a field as
null→ set to null. A field that is required, or that cannot change once set, rejectsnullwith a400— the same as omitting a required field on create.
A create is not sparse: it must include every required field, because there is no existing record to fall back to.
Nested objects
Update a price tier's conditions — set one filter, clear another, leave the rest
// POST /public/v1/price-tiers
{
"id": "8f3c…",
"conditions": {
"one_of_product_ids": ["0c9e…"], // set this filter
"one_of_company_ids": [] // clear this filter
// every other condition is left untouched
}
}A nested object is sparse the same way: send only the keys you want to change and
omit the rest. To clear a single key without touching the others, send it as null;
a list-valued key (like the condition filters below) is also cleared by an empty
array [].
Collections (line items, charges, …)
Add one charge, keep an existing one, and drop any others — while leaving line items untouched
// POST /public/v1/purchases
{
"id": "8f3c…",
"charges": [
{"id": "a1b2…"}, // keep as-is
{
"name": "Fuel Surcharge",
"price": "5.00",
"type": "CHARGE",
"unit_type": "PRICE"
} // add
// any other existing charge is deleted
]
// "items" omitted entirely → the line items are left untouched
}A collection — such as a purchase's items and charges — is optional on
update: omit the whole field to leave its existing entries exactly as they are.
When you do send a collection, it is the complete set for the record:
- An entry with an existing
idis patched — merged onto the stored entry, so you can change one field and omit the rest. - An entry without an
idis added as a new entry. - Any existing entry whose
idyou do not include is deleted. - An empty array clears the whole collection (or fails if clearing is not allowed).
Because a collection deletes by omission, you cannot separately mark a single entry for deletion — you drop it by leaving it out of a sent collection.
Assemblies are different
The assembly upsert (POST /public/v1/assemblies) does not follow the
omit-to-delete model above. It is driven by an explicit per-row action
(CREATE, UPDATE, or DELETE) on the assembly and its nested rows. Before
integrating against it, read the assemblies endpoint docs
specifically — the rules in this section do not apply.
All other upsert endpoints follow the rules above; where one differs, its own endpoint docs call it out, so check them before you rely on this behavior.