API

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, rejects null with a 400 — 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 id is patched — merged onto the stored entry, so you can change one field and omit the rest.
  • An entry without an id is added as a new entry.
  • Any existing entry whose id you 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.

On this page