Upsert an order
Create or update a single sales order. Omit id to create a new order (Distru assigns the order number and id); pass an existing order's id to update it. An id that doesn't exist, or belongs to another company, returns a not-found error.
Updates are sparse at the top level: send only the fields you want to change — every field you omit (including status) keeps its current value. The items and charges collections work differently. Omit the whole items (or charges) field and its existing rows are left untouched. Send the field and it fully replaces that set: an existing row whose id you omit is deleted, an entry whose id matches an existing row updates it (omitted fields on that entry are kept from the existing row, so you can patch a single field by sending just its id and the change), and an entry whose id is new (or omitted — Distru then assigns one) is added. Sending items as an empty array removes every line, which an order cannot be left in, so it is rejected. Charges are full-replace on update — see the charges field for how existing charges, presets, and deletions are handled; on create, charge presets whose auto-apply tags match the order's products are added automatically. The whole upsert is atomic: if any part is rejected (a validation error, a disallowed status transition, an unfulfillable line) nothing is changed and the response is a 400 whose errors point at the offending field.
Setting status moves the order through its lifecycle and drives inventory. A PENDING or CANCELED order reserves nothing and touches no inventory; moving to PROCESSING commits sellable inventory — assigning a package or batch to a line item moves that quantity into a committed selling state and any unfulfilled line adds to the product's reserved quantity; READY_TO_SHIP, DELIVERING, DELIVERED, and COMPLETED additionally require every line item to be fulfilled. See the status field on the order for the full per-status behavior and transition requirements.
Compliance: associating the order with a state transfer is one-system-only — send metrc_transfer_id OR biotrack_id, never both. Doing so builds the order from that outgoing Metrc/BioTrack transfer. Any order carrying package-tracked items must be associated with a compliance transfer before it can reach DELIVERING, DELIVERED, or COMPLETED.
Invoicing: set upsert_invoice to create or refresh this order's invoice, and email_invoice to email it. The response returns the saved order with its recomputed total, line items (with cost fields), charges, invoices, and returns.
Required permission: orders_permissions_create to create, orders_permissions_edit (plus access to the order under team restrictions) to update.
BearerAuthorizationBearer <token>API token generated in Distru (Settings → Distru API). Send it on every request as Authorization: Bearer YOUR_API_TOKEN.
application/json- body
biotrack_id?stringThe BioTrack manifest to associate with this order, building the order from that outgoing BioTrack transfer. Mutually exclusive with metrc_transfer_id — send at most one; an order can be linked to only one compliance transfer.
blaze_payment_type?stringThe payment type for an order shipping to a Blaze-associated company. Required (and only meaningful) when the order's buyer company is mapped to a Blaze retailer through the Distru integration; leave it off otherwise.
"CASH""CREDIT""DEBIT""COD""ACH_TRANSFER""CHEQUE""OTHER"company_id?stringThe buyer of this order, as a company relationship ID (the same id in each order's company.id and GET /public/v1/companies). Determines the customer, and drives pricing, default payment term (used to derive due_datetime), and blaze_payment_type requirements. Optional while the order stays PENDING, PROCESSING, or CANCELED, but required to move it to READY_TO_SHIP, DELIVERING, DELIVERED, or COMPLETED. Once a customer is set, you cannot clear it back to null on a later update.
delivery_datetime?stringISO8601 datetime the order was / will be delivered. Optional; null when the order has no delivery datetime set.
due_datetime?stringThe datetime by which the customer is expected to pay for this order. Optional: when omitted, it is derived from the customer's default payment term, then the company default order payment term, then falls back to the order date (COD).
external_notes?stringThis is a message that will be shown to the customer on order slips. This is the "Message to Customer" field in the Distru order form.
upsert_invoice?booleanWhen true, create an invoice for this order if it doesn't have one yet, or update the existing invoice with the order's latest changes.
email_invoice?booleanWhen true, email the order's invoice. No email is sent unless the order has an invoice (see upsert_invoice) and a recipient can be resolved from email_invoice_addresses or the buyer company relationship's invoice email.
email_invoice_addresses?stringComma-separated list of email addresses to send the invoice to when email_invoice is true. Takes precedence over the company relationship's invoice email. Invalid addresses are rejected.
id?stringID for this order. Omit it to create a new order — Distru assigns the ID. Provide an existing order's ID to update that order; an ID that doesn't exist returns a not-found error.
order_datetime?stringISO8601 datetime the order was placed. Required on create — omitting it there returns a validation error; on update, omit to leave the existing value unchanged. This is the field the list endpoint sorts (newest first) and filters on.
charges?array<>The order's complete set of charges and discounts — the extra lines (fees, discounts, taxes) added on top of the items. Each entry follows the OrderChargeRequest shape.
Replacement semantics: on update the charges you send become the order's entire set — any existing charge you don't include is deleted. Omit the charges field entirely to leave the current charges unchanged; send [] to remove them all. Each charge is matched to an existing one by id — a charge with a new or absent id is added, a charge with a known id is updated, and on a matched charge any field you omit keeps its current value — so sending just {"id": "..."} keeps that charge exactly as it is. You never need to resend a charge's full payload to preserve it; include its id and only the fields you want to change.
Manual charges: provide name, type, unit_type, and the amount. For a PERCENT charge the amount is calculated from the order subtotal and any price you send is ignored; send percent instead.
Preset charges: set charge_preset_id and leave name and type out — Distru fills them from the charge preset. If the preset allows inline edits you may also set unit_type and the amount (price or percent); otherwise send only charge_preset_id. Sending a field the preset controls returns a 400.
charge_preset_id is fixed once a charge exists. Omitting charge_preset_id on an update keeps the charge's current preset, like any other omitted field. But you cannot change it: sending a different charge_preset_id, sending null to detach a preset, or adding a charge_preset_id to a charge that was manual all return a 400. To switch a charge to a different preset or to a manual charge, drop it (omit its id, which deletes it) and add a new charge.
Auto-apply: on order create, presets whose auto-apply tags match the order's products are added automatically (see the preset's auto_apply_tags_on_sales_orders flag); a preset you also send explicitly is applied once. Updates never auto-apply: adding items later does not add new preset charges (existing PERCENT charges still recompute from the new subtotal; fixed-price ones are unchanged). The reverse also holds: an auto-applied charge is never auto-removed — deleting the line items whose product tags added it leaves the charge in place until you drop it yourself (omit its id from a charges update).
items?array<>The products being sold on this order, one entry per line. Required on create. On update it is optional: omit it to leave the order's existing lines untouched, or send it to fully replace the line set — an existing line whose id you omit is deleted, an entry whose id matches an existing line updates it (fields you omit on the entry keep their current value, so an id-only entry is a no-op), and an entry with a new or omitted id is added. An empty array is rejected because every order must keep at least one item. If the order is matched with a compliance transfer, its package-tracked lines each map one-to-one to a transferred package and cannot be deleted — omitting one is rejected. Each entry follows the OrderItemRequest shape.
internal_notes?stringFree-form notes visible only inside Distru; never shown to the customer. Use external_notes for a customer-facing message.
metrc_transfer_id?integerThe Metrc transfer to associate with this order, building the order from that outgoing Metrc transfer. This is Metrc's own integer transfer id, not a Distru ID. Mutually exclusive with biotrack_id — send at most one; an order can be linked to only one compliance transfer.
location_id?stringThe Distru location ID of the order's top-level location — used to filter orders and to associate the order with a compliance (Metrc) license, identifying which Metrc license the order takes place under. It is not the location sale quantities are drawn from; each order item sets that via its own location_id. Optional in general, but required when the order has any package-tracked items, where it must reference a location that has a compliance license and that license must match the license of every package-tracked item's location.
billing_location_id?stringThe billing location's ID (a Distru location ID). Optional.
shipping_location_id?stringThe shipping location's ID (a Distru location ID). Optional.
owner_id?stringThe Distru user that owns this order, as a user ID (the same id in the response's owner.id). Optional.
status?stringThe status to set for this order, controlling where it sits in its lifecycle and how it affects inventory and compliance. Required on create; on update, omit to leave the current status unchanged. See the status field on the order response for what each value means. Note that some transitions have requirements: moving to READY_TO_SHIP, DELIVERING, DELIVERED, or COMPLETED requires every line item to be fulfilled and a customer (company_id) to be set, and DELIVERING/DELIVERED/COMPLETED additionally require a compliance transfer when the order carries any package-tracked items.
"PENDING""PROCESSING""READY_TO_SHIP""DELIVERING""DELIVERED""COMPLETED""CANCELED"custom_data?A map of custom field IDs to their values. Use GET /public/v1/custom-fields?parent_object=order to retrieve available custom fields, their IDs, and their types. The value format depends on the field's type: a text field takes a string, a date field takes a full ISO8601 datetime, and a checkbox field takes an array of its selected options.
A single order
*/*- response
A single order wrapped in a data envelope
data?A sale of products to a customer. Holds the line items sold, their quantities and prices, any extra charges/discounts/taxes, delivery and fulfillment details, and links to the resulting invoices and returns.
curl -X POST 'https://app.distru.com/public/v1/orders' \ -H 'Content-Type: application/json' \ -d '{ "billing_location_id": "00000000-0000-0000-0000-0000000004e9", "charges": [ { "name": "C1", "percent": "10.0000", "type": "CHARGE", "unit_type": "PERCENT" }, { "name": "C2", "price": "-5.0000", "type": "DISCOUNT", "unit_type": "PRICE" } ], "company_id": "00000000-0000-0000-0000-000000000921", "delivery_datetime": "2020-01-01T00:00:00.000000Z", "due_datetime": "2020-01-01T00:00:01.000000Z", "external_notes": "Thank you for ordering!", "internal_notes": "Internal notes for this order", "items": [ { "location_id": "00000000-0000-0000-0000-0000000004e8", "price_base": "10.000000000", "product_id": "29aa99b0-36f0-46a1-ab81-3e03a2f54abf", "quantity": "1.000000000" } ], "order_datetime": "2020-01-01T00:00:02.000000Z", "owner_id": "00000000-0000-0000-0000-000000001403", "shipping_location_id": "00000000-0000-0000-0000-0000000004e9", "status": "PROCESSING"}'{ "data": { "billing_location": { "address": "123 Fake Street, Beverly Hills, CA 90210, US", "company": { "id": "00000000-0000-0000-0000-00000001b73d", "name": "Company 5147", "updated_datetime": "2026-11-05T11:00:20.210137Z" }, "company_id": "00000000-0000-0000-0000-00000002d440", "id": "00000000-0000-0000-0000-0000000124e0", "license_id": null, "license_number": null, "name": "Place 1722" }, "biotrack_id": null, "blaze_payment_type": null, "buyer_company": null, "buyer_note": null, "charges": [ { "charge_preset": null, "id": "b0f31b30-1c44-482e-998d-03cbc3faa408", "inserted_datetime": "2026-11-05T11:00:20.280536Z", "name": "C1", "percent": "10.0000", "price": "1.00", "type": "CHARGE", "unit_type": "PERCENT" }, { "charge_preset": null, "id": "ef6f8f12-8403-4497-b047-67e2f26dec42", "inserted_datetime": "2026-11-05T11:00:20.283215Z", "name": "C2", "percent": null, "price": "-5.00", "type": "DISCOUNT", "unit_type": "PRICE" } ], "combined_order": null, "company": { "id": "00000000-0000-0000-0000-00000001b73d", "name": "Company 5147", "updated_datetime": "2026-11-05T11:00:20.210137Z" }, "creator": { "banned": false, "deleted_at": null, "email": "[email protected]", "full_name": "John Foo", "id": "00000000-0000-0000-0000-00000003eb9a", "inserted_datetime": "2026-11-05T11:00:20.230818Z", "role": { "id": "00000000-0000-0000-0000-0000000409cc", "name": "Admin 7775" }, "updated_datetime": "2026-11-05T11:00:20.230818Z" }, "custom_data": [ { "id": 2422, "name": "Custom Field 105", "value": null } ], "delivered_datetime": null, "delivery_datetime": "2020-01-01T00:00:00.000000Z", "due_datetime": "2020-01-01T00:00:01.000000Z", "external_notes": "Thank you for ordering!", "id": "042ed508-1c94-4933-be2a-52126f4681ba", "inserted_datetime": "2026-11-05T11:00:20.279658Z", "internal_notes": "Internal notes for this order", "inventory_source": null, "invoices": [], "items": [ { "batch": null, "compliance_quantity": null, "cost_per_unit": null, "cost_per_unit_default": null, "id": "27ffe0be-f229-4f97-ab8c-c36772442d14", "inserted_datetime": "2026-11-05T11:00:20.284020Z", "is_sample": false, "leaflink_id": null, "location": { "address": "123 Fake Street, Beverly Hills, CA 90210, US", "company_id": "00000000-0000-0000-0000-00000002d43f", "id": "00000000-0000-0000-0000-0000000124df", "license_id": "00000000-0000-0000-0000-000000006ea6", "name": "Place 1721" }, "note": null, "package": null, "price": "10.000000000", "price_base": "10.000000000", "price_tier_mode": "AUTO", "price_tier_version": null, "product": { "id": "6113c490-3cff-4ac1-a70b-84b13c1db32d", "name": "P1", "sku": "SKU1", "updated_datetime": "2026-11-05T11:00:20.241537Z" }, "quantity": "1.000000000", "returned_quantity": "0", "thc_percentage_total": null, "total_cost_actual": null, "total_cost_default": null } ], "leaflink_id": null, "leaflink_order_number": null, "location": null, "menu": null, "metrc_transfer_id": null, "metrc_transfer_template_error": null, "metrc_transfer_template_id": null, "metrc_transfer_template_status": null, "order_datetime": "2020-01-01T00:00:02.000000Z", "order_number": "SO-0000001", "owner": { "banned": false, "deleted_at": null, "email": "[email protected]", "full_name": "John Foo", "id": "00000000-0000-0000-0000-00000003eb9a", "inserted_datetime": "2026-11-05T11:00:20.230818Z", "role": { "id": "00000000-0000-0000-0000-0000000409cc", "name": "Admin 7775" }, "updated_datetime": "2026-11-05T11:00:20.230818Z" }, "payment_term_name": null, "returns": [], "shipping_location": { "address": "123 Fake Street, Beverly Hills, CA 90210, US", "company": { "id": "00000000-0000-0000-0000-00000001b73d", "name": "Company 5147", "updated_datetime": "2026-11-05T11:00:20.210137Z" }, "company_id": "00000000-0000-0000-0000-00000002d440", "id": "00000000-0000-0000-0000-0000000124e0", "license_id": null, "license_number": null, "name": "Place 1722" }, "status": "PROCESSING", "tasks": [], "total": "6.00", "updated_datetime": "2026-11-05T11:00:20.314645Z" }}