Create, update or delete a sales order

distru-mutate-sales-orderWriteLeafLinkMetrcPOSQuickBooks

Creates, updates or deletes one sales order. An UPDATE pushes the change to LeafLink for an order that came from it, and re-syncs the order's invoices to QuickBooks and to your POS (Dutchie, Blaze or Treez) when they sync there. On an order with a Metrc transfer template, an UPDATE with every line fulfilled updates the template in Metrc, and canceling or deleting the order deletes it.

Inputs

Every tool also takes the common inputs.

FieldTypeRequiredDescription
actionenum
CREATEUPDATEDELETE
Yes
idstringNoRequired for UPDATE and DELETE. Resolve via distru-search-sales-orders.
customerIdstringNoCustomer company. Optional while the status is PENDING or PROCESSING; required from READY_TO_SHIP on. Resolve via distru-search-companies.
statusenum
PENDINGPROCESSINGREADY_TO_SHIPDELIVERINGDELIVEREDCOMPLETEDCANCELED
NoCREATE defaults to the company's default status, usually PENDING. PROCESSING holds stock only for lines with a batch or package: to reserve it, give each line one. CANCELED releases stock, deletes pending assemblies and voids every invoice of the order; moving out of CANCELED un-voids them. CANCELED, like DELETE, also deletes the order's Metrc transfer template; every UPDATE re-syncs a LeafLink order and the order's invoices to QuickBooks and the POS when they sync there. A LeafLink order can't leave CANCELED.
orderDatetimestringNoCREATE defaults to now. ISO 8601 with a UTC offset, e.g. "2026-09-01T00:00:00-07:00"; the company timezone is on distru-get-me.
dueDatetimestringNoPayment due. Defaults to orderDatetime plus the customer's payment term, else the company's default term, else orderDatetime. ISO 8601 with a UTC offset, e.g. "2026-09-01T00:00:00-07:00"; the company timezone is on distru-get-me.
deliveryDatetimestringNoWhen the company's compliance setting to use the received date is on, matching a received manifest replaces it with that date. ISO 8601 with a UTC offset, e.g. "2026-09-01T00:00:00-07:00"; the company timezone is on distru-get-me.
locationIdstringNoInventory location stock ships from; new rows without their own locationId use it. On UPDATE, rows already on the order keep their location: send each row by id with its new locationId (and a batch held there) to move it; a row sent without id is a new row. Required for metrcPackageLabel rows: a label is looked up on its license, the one a manifest must belong to. A packageId row needs it or its own locationId. Resolve via distru-search-locations.
billingLocationIdstringNoThe customer's billing location. Resolve via distru-get-company (includeLocations) on the customer.
shippingLocationIdstringNoThe customer's shipping location. Resolve via distru-get-company (includeLocations) on the customer.
ownerIdstringNoSales rep. Resolve via distru-search-users.
internalNotesstringNoNot shown to the customer.
externalNotesstringNoShown on the order slip and invoice.
metrcManifestNumberstringNoOutgoing Metrc manifest to match. Resolve via distru-search-metrc-manifests (direction OUTGOING); read its packages with distru-get-metrc-manifest (includeDeliveries). Status defaults to the company's post-match status when omitted.
orderItemsobject[] or nullNoRequired for CREATE. One entry per stored row. On UPDATE the complete list: stored rows left out are deleted and their stock released; send a stored row with just its id to keep it unchanged. Omitted or null leaves the rows as they are. Read current rows with distru-get-sales-order first (includeOrderItems); lineGroup there is output only, never sent back. Groups: a product line fulfilled from packages is several rows sharing product, batch, location, priceBase, isSample and tier (and THC when the company sells by potency; same lineGroup in the output): one row per package (whole package, no quantity) and at most one remainder row with only quantity, allowed in PENDING and PROCESSING. Adding a package row does not reduce the remainder: resend the remainder with the quantity left, or leave it out when the packages cover the line. Dropping a package row shrinks the line unless you add its quantity back to the remainder.
orderItems[].idstringNoUPDATE only: a row already on the order; only the fields sent change. Omit to add a row. Resolve via distru-get-sales-order (includeOrderItems).
orderItems[].productIdstringNoRequired unless batchId, packageId or metrcPackageLabel gives the product. Alone on a batch- or package-tracked product: an unfulfilled row that holds no stock (the remainder). A new productId on a stored row with a batch or package needs a batch or package of the new product too. Resolve via distru-search-products.
orderItems[].batchIdstringNoFulfill from this batch. Batch-tracked products only. Resolve via distru-search-batches.
orderItems[].packageIdstringNoSell this whole package; its quantity, product and batch come from it, so send it without batchId. Package-tracked products only. Resolve via distru-search-packages.
orderItems[].metrcPackageLabelstringNoSell this whole package by its Metrc label. With metrcManifestNumber (sent or stored): a package on that manifest, created in Distru at match when it isn't there yet (package null on distru-get-metrc-manifest; then productId is needed unless its Metrc item was mapped to a product before). Without: a package already in Distru on the license of the order's locationId.
orderItems[].quantitystring or numberNoIn the product's unit type. Required to add a product-only or batch row; refused on a package or label row, which sells the whole package.
orderItems[].priceBasestring or numberNoUnit price before price tiers, in the product's unit type. Required to add a row; a sample keeps it unless you send 0. Rows of one line share it. Never send a row's discounted price back as priceBase. Without the Edit Product Price permission a stored row's priceBase can't change, and a new row, or one changing product, takes the product's price (the error names it).
orderItems[].priceTierModeenum
AUTOOVERRIDENONE
NoAUTO (default) applies the best matching tier and locks its current version, so the row then reads OVERRIDE; OVERRIDE locks priceTierId; NONE applies none. A stored row keeps its tier unless this is sent. Tiers apply only once the order has a customer, and are picked on the line's total across its rows.
orderItems[].priceTierIdstringNoWith priceTierMode OVERRIDE only. A row already locked to this tier keeps its version. Resolve via distru-search-price-tiers.
orderItems[].locationIdstringNoWhere this row's stock is held; a new row defaults to the order's locationId. Resolve via distru-search-locations.
orderItems[].isSamplebooleanNoSample: no tax. Its price is still priceBase; send 0 only to give it free.
orderItems[].notestringNo"" clears it.
orderChargesobject[] or nullNoOrder-level charges, discounts and taxes: a preset (chargePresetId) or a custom one (name, type, unitType, and percent or price). On UPDATE the complete list: stored charges left out are deleted; [] removes them all. Omitted or null leaves the charges as they are. Read current rows with distru-get-sales-order first (includeOrderCharges).
orderCharges[].idstringNoUPDATE only: a charge already on the order; only the fields sent change. Omit to add a charge. Resolve via distru-get-sales-order (includeOrderCharges).
orderCharges[].chargePresetIdstringNoA charge preset; it sets name, type and the amount, so send it alone: unitType, percent and price are refused unless the preset allows inline edits. Can't change once the charge exists. Resolve via distru-search-lookups (type CHARGE_PRESET).
orderCharges[].namestringNo
orderCharges[].typeenum
CHARGEDISCOUNT
NoForced to CHARGE with taxId.
orderCharges[].unitTypeenum
PERCENTPRICE
No
orderCharges[].percentstring or numberNoPercent of the order subtotal for unitType PERCENT, e.g. 10. A custom charge keeps the sign sent, whatever its type: negative for a DISCOUNT, e.g. -10. With chargePresetId, only to override a preset that allows inline edits, and positive.
orderCharges[].pricestring or numberNoFlat amount for unitType PRICE. A custom charge keeps the sign sent, whatever its type: negative for a DISCOUNT. With chargePresetId, only to override a preset that allows inline edits, and positive.
orderCharges[].taxIdstring or nullNoMakes the charge a tax. Not with chargePresetId. Resolve via distru-search-lookups (type TAX). Send null to clear it.
customFieldsobject[]NoOnly the fields listed change. Required custom fields must be set, on update too, while still blank.
customFields[].customFieldIdstringYesResolve via distru-search-custom-fields (entityType SALES_ORDER).
customFields[].valuestring or string[]YesTEXT: the text. DROPDOWN: the option name. CHECKBOX: an array of option names. DATE: YYYY-MM-DD, company timezone. "" or [] clears the field.

Output

This tool returns one of 2 shapes, depending on its inputs.

Presence on a nested field assumes its parent exists.

Result with salesOrder

FieldTypePresenceDescription
salesOrderobjectAlways
salesOrder.idstringAlways
salesOrder.customFieldsobject[]Always
salesOrder.customFields[].customFieldobjectAlways
salesOrder.customFields[].customField.idstringAlways
salesOrder.customFields[].customField.namestringAlways
salesOrder.customFields[].customField.fieldTypeenum
CHECKBOXDATEDROPDOWNTEXT
Always
salesOrder.customFields[].valuestringCan be null
salesOrder.orderNumberstringAlways
salesOrder.statusstringAlways
salesOrder.totalstringAlways
salesOrder.totalChargesstringAlways
salesOrder.totalReturnedstringAlways
salesOrder.orderDatestringAlways
salesOrder.dueDatestringAlways
salesOrder.deliveryDatestringCan be null
salesOrder.createdDatestringAlways
salesOrder.updatedDatestringAlways
salesOrder.externalNotesstringCan be null
salesOrder.internalNotesstringCan be null
salesOrder.urlstringAlways
salesOrder.coststringCan be null
salesOrder.revenuestringCan be null
salesOrder.customerobjectNull or absent
salesOrder.customer.idstringAlways
salesOrder.customer.namestringNull or absent
salesOrder.ownerobjectCan be null
salesOrder.owner.idstringAlways
salesOrder.owner.profileobjectAlways
salesOrder.owner.profile.fullNamestringAlways
salesOrder.locationobjectCan be null
salesOrder.location.idstringAlways
salesOrder.location.namestringAlways
salesOrder.location.addressstringAlways
salesOrder.shippingLocationobjectCan be null
salesOrder.shippingLocation.idstringAlways
salesOrder.shippingLocation.namestringAlways
salesOrder.shippingLocation.addressstringAlways
salesOrder.billingLocationobjectCan be null
salesOrder.billingLocation.idstringAlways
salesOrder.billingLocation.namestringAlways
salesOrder.billingLocation.addressstringAlways
salesOrder.menuobjectCan be null
salesOrder.menu.idstringAlways
salesOrder.menu.namestringAlways
salesOrder.licenseobjectCan be null
salesOrder.license.idstringAlways
salesOrder.license.licenseNumberstringAlways
salesOrder.combinedOrderobjectCan be null
salesOrder.combinedOrder.idstringAlways
salesOrder.combinedOrder.orderNumberstringAlways
salesOrder.combinedOrder.statusstringAlways
salesOrder.orderItemsobject[]Always
salesOrder.orderItems[].idstringAlways
salesOrder.orderItems[].quantitystringAlways
salesOrder.orderItems[].metrcQuantitystringCan be null
salesOrder.orderItems[].pricestringAlways
salesOrder.orderItems[].priceBasestringAlways
salesOrder.orderItems[].isSamplebooleanAlways
salesOrder.orderItems[].notestringCan be null
salesOrder.orderItems[].coststringCan be null
salesOrder.orderItems[].revenuestringCan be null
salesOrder.orderItems[].productobjectAlways
salesOrder.orderItems[].product.idstringAlways
salesOrder.orderItems[].product.namestringAlways
salesOrder.orderItems[].product.unitTypeobjectAlways
salesOrder.orderItems[].product.unitType.idstringAlways
salesOrder.orderItems[].product.unitType.namestringAlways
salesOrder.orderItems[].batchobjectCan be null
salesOrder.orderItems[].batch.idstringAlways
salesOrder.orderItems[].batch.namestringAlways
salesOrder.orderItems[].locationobjectCan be null
salesOrder.orderItems[].location.idstringAlways
salesOrder.orderItems[].location.namestringAlways
salesOrder.orderItems[].packageobjectCan be null
salesOrder.orderItems[].package.idstringAlways
salesOrder.orderItems[].package.metrcLabelstringAlways
salesOrder.orderItems[].priceTierModeenum
AUTONONEOVERRIDE
Null or absent
salesOrder.orderItems[].lineGroupintegerAlwaysRows with the same lineGroup are one line: same product, batch, location, priceBase, sample flag and tier (and THC when the company sells by potency), e.g. one row per package and the unfulfilled remainder. Numbered from 1 in row order on this order only.
salesOrder.orderItems[].priceTierobjectNull or absent
salesOrder.orderItems[].priceTier.idstringAlways
salesOrder.orderItems[].priceTier.namestringAlways
salesOrder.orderItems[].priceTier.pricingobjectAlways
salesOrder.orderItems[].priceTier.pricing.typeenum
AMOUNT_OFFFLAT_PRICEPERCENT_OFF
Always
salesOrder.orderItems[].priceTier.pricing.valuestringCan be null
salesOrder.orderItems[].priceTier.isLivebooleanAlways
salesOrder.orderChargesobject[]Always
salesOrder.orderCharges[].idstringAlways
salesOrder.orderCharges[].namestringAlways
salesOrder.orderCharges[].typestringAlways
salesOrder.orderCharges[].unitTypestringAlways
salesOrder.orderCharges[].percentstringCan be null
salesOrder.orderCharges[].pricestringAlways
salesOrder.orderCharges[].isTaxbooleanAlways
salesOrder.complianceManifestNumberstringCan be nullManifest number of the matched compliance transfer; null when unmatched.

Result with id

FieldTypePresence
idstringAlways
What the AI reads

Creates, updates or deletes one sales order: a sale to a customer with line items, order-level charges or discounts and custom fields. Stock is held from PROCESSING on, only by lines fulfilled from a batch or package; PENDING and CANCELED hold none. READY_TO_SHIP and later need a customer and every batch- or package-tracked line fulfilled. A line with only a product holds no stock (the company's auto-fulfill setting fills it at PROCESSING): to sell from a location, give each line a batch stocked there (distru-search-batches) or a package. A package line sells the whole package: pass packageId or metrcPackageLabel, never quantity. One product line fulfilled from several packages is several rows sharing product and priceBase (lineGroup in the output); see orderItems. To match an outgoing Metrc manifest set metrcManifestNumber: the package lines must be the manifest's packages (or a subset when the company allows partial matching); packages not yet in Distru are created. Matching can't be undone here; a matched order can't go back to PENDING or to CANCELED and keeps its package lines. CREATE adds preset charges whose auto-apply tags match the products. Invoices: distru-create-invoice; an existing invoice follows line price changes and quantity decreases. DELETE removes the order for good with its lines, charges and shipments and releases its stock; refused for merged orders, orders with returns, orders matched to a transfer and orders with an invoice (delete the invoices first with distru-delete-invoice, or set status CANCELED, which voids them). CREATE and UPDATE return the order as distru-get-sales-order does with includeOrderItems, includeOrderCharges and includeOrderItemPriceTiers; DELETE returns its id. Sparse update: on UPDATE only the fields passed change; orderItems and orderCharges, when passed, replace the whole list — read the order with distru-get-sales-order first and resend every line you keep.