API Overview
New to v2? Start with the ordering guide or purchase-order guide below.
The KSS API provides programmatic access to product catalog, inventory, customer, invoice, and pricing data. All requests are made over HTTPS and return JSON responses.
Import an OpenAPI specification into Postman
There are two environments, each backed by its own isolated database and requiring a separate API key.
All endpoints are versioned under /api/v2. A full request to the products endpoint looks like:
GET https://api.kssdata.com/api/{{version}}/products?States=CA&Statuses=1
x-api-key: your-api-key
Authentication
Every request must include your API key in the x-api-key request header. Requests without a valid key return 401 Unauthorized.
x-api-key: your-api-key
Employee — Full access to all endpoints and all data across any state or customer.
Customer — Access is scoped to the customer accounts associated with the key. Requests for data outside that scope are silently filtered or return 403.
Supplier — Access is scoped to the supplier accounts associated with the key. Some endpoints are unavailable to Supplier keys entirely, noted per endpoint below.
Idempotency
For POST and PATCH requests, you may send an Idempotency-Key request header to make retries safe after a timeout or lost response.
Use a unique key for each logical operation, such as a UUID generated by your client. If the same API key sends the same idempotency key with the same method, path, query, and body, the API returns the original response instead of running the operation again.
Do not reuse a key for a different operation. Reusing a key with a different request returns 409 IDEMPOTENCY_KEY_REUSED. A retry while the original request is still processing returns 409 IDEMPOTENCY_KEY_IN_PROGRESS.
POST /api/{{version}}/purchases
x-api-key: your-api-key
Idempotency-Key: 9f30f7f5-7e08-4f6a-a60c-8f7d11e9c702
Idempotency-Replayed: true. Keep your own idempotency keys long enough to cover your retry window.Rate Limiting
Rate limits are enforced per API key. Each key has a configurable maximum requests per hour. When the limit is reached, the API returns 429 Too Many Requests.
Every response reports your current standing so you can pace requests before you are throttled:
0 means the next request may be rejected.429 only: seconds to wait before retrying.When you receive a 429, wait the number of seconds given in Retry-After before sending another request rather than retrying immediately.
If you are building a sync process that requires high request volume, contact KSS to discuss your needs.
RateLimit-Limit: 1000
RateLimit-Remaining: 847
RateLimit-Reset: 2934
Pagination
All list endpoints support pagination via Page and PageSize query parameters. The default page size is 50. The maximum page size is 500.
Paginated responses wrap results in a Data array alongside Page and PageSize fields. A HasNextPage boolean indicates whether more results exist beyond the current page.
GET /api/{{version}}/products?States=CA&Page=2&PageSize=100
{
"Data": [ ... ],
"Page": 2,
"PageSize": 100,
"HasNextPage": true
}
Errors
The API uses standard HTTP status codes. Error responses include a JSON body with an error field describing the issue.
{
"Error": "InvoiceIDs parameter is required"
}
Response Headers
Alongside the rate limit headers, every response includes the following.
Each response carries an X-Request-Id header uniquely identifying that request. Include this value when contacting KSS support so we can locate the exact request in our logs.
If you send your own X-Request-Id on a request, we echo it back unchanged (when it is a short alphanumeric token), letting you correlate a request across your systems and ours.
Responses include an ETag validator. To avoid re-downloading data that has not changed, send the ETag from a previous response back on your next request using the If-None-Match header. If the data is unchanged, the API returns 304 Not Modified with an empty body; otherwise it returns the full response with a new ETag.
Responses are marked private and must not be shared between different API keys in a downstream cache.
GET /api/{{version}}/products?States=CA
x-api-key: your-api-key
If-None-Match: W/"a1b2c3d4e5f6..."
Endpoints
Getting Started: Create a purchase order
A purchase order describes incoming stock from a vendor to a receiving location. Build a draft, submit it, then follow its processing progress.
1. Gather the details
You'll need the vendor, receiving location, supplier, PO number, and products with their quantities. Add the delivery date and any required batch or expiration details.
Use a PO number that hasn't already been used for that vendor. Purchase orders are available to supplier and employee keys.
2. Save and review
Create a draft and keep its purchase identifier. Read it back to review the details, and edit it while it is still a draft. You can also validate the proposed purchase before creating it.
3. Submit it
Submit the draft when it's ready. The API checks it again, then queues it for background processing. You can also create and submit a complete purchase in one request.
4. Check the result
Read the purchase using the identifier you saved to follow its status. Processing can take time, and some purchases may need product activation before they can finish. If the purchase is rejected, investigate the reported problem before retrying.
Make changes before submitting
You can edit or delete a draft purchase. Once submitted, it can no longer be edited or deleted through these endpoints. Review the details before submitting.
Returns purchase orders filtered by purchase ID, vendor, location, status, and date range. purchase_transactions are included by default; send include_transactions=false to return headers only. Results cover every purchase order you can access at every stage, from drafts that have not been submitted yet through processed purchases, in one paginated list, and each purchase order appears once. A status filter returns only purchases currently in one of the requested statuses. Not accessible to Customer API keys.
| Name | Type | Required | Description |
|---|---|---|---|
|
purchase_ids
query
|
string | optional |
Comma-separated list of purchase identifiers to filter by. Each value may be a numeric purchase_id or a purchase_global_id.
|
|
vendor_ids
query
|
string | optional |
Comma-separated list of vendor IDs to filter by.
|
|
location_ids
query
|
string | optional |
Comma-separated list of destination location IDs to filter by.
|
|
statuses
query
|
string | optional |
Comma-separated list of purchase statuses to filter by. Omitted, purchases in every status are returned. InProgress is an editable draft; Submitted is queued for processing; Importing is being processed; PendingActivation is waiting on one or more products to be activated before processing can finish; New, Accepted, Received, Confirmed, and Verified are the stages of a processed purchase order.
|
|
start_date
query
|
date | optional |
Calendar date formatted YYYY-MM-DD. Returns processed purchases with a post_date on or after this date, and purchases not yet processed with a delivery_date on or after this date. An empty value is ignored; any other value that is not a valid YYYY-MM-DD date returns 400.
|
|
end_date
query
|
date | optional |
Calendar date formatted YYYY-MM-DD. Returns processed purchases with a post_date on or before this date, and purchases not yet processed with a delivery_date on or before this date. An empty value is ignored; any other value that is not a valid YYYY-MM-DD date returns 400.
|
|
include_transactions
query
|
boolean | optional |
Line items are included by default. Send false to omit purchase_transactions from each purchase.
|
|
page
query
|
number | optional |
Page number to retrieve. Defaults to 1.
|
|
page_size
query
|
number | optional |
Number of rows to return per page. Defaults to 50; maximum is 500.
|
Supports Page and PageSize query parameters. Default page size: 50. Maximum page size: 500 — larger values are reduced to 500.
| Status | Description |
|---|---|
| 200 | OK |
| 400 | The request is missing required input or contains invalid input. |
| 401 | The request is missing a valid API key. |
| 403 | The API key is not authorized to access this resource. |
| 429 | Rate limit exceeded. |
| 500 | Unexpected server error. |
{
"data": [
{
"purchase_id": 70001,
"receive_date": "2026-06-02T06:00:00.000Z",
"shipment_num": "Example PO 70001",
"shipment_id": 72001,
"to_location_id": 40001,
"to_location_name": "Alameda",
"vendor_name": "EXAMPLE DISTRIBUTORS LLC (Example Location)",
"vendor_id": 50001,
"post_date": "2026-06-02T06:00:00.000Z",
"status": "Verified",
"po_num": "Example PO 70001",
"freight": "0.00",
"tax": "0.00",
"other_cost": "0.00",
"total": "35726.38",
"total_cases": "184.00",
"public_pdf_link": null,
"purchase_pdf_url_api_key_id": 94001,
"memo": null,
"last_edit_time": null,
"last_calc_time": "2026-06-04T10:20:14.000Z",
"time_updated": "2026-08-21T18:06:57.930Z",
"invoice_date": "2026-06-01T06:00:00.000Z",
"due_date": "2026-08-30T06:00:00.000Z",
"terms": "Driver Accounts",
"term_id": 80002,
"time_created": "2026-06-15T22:09:09.921Z",
"purchase_global_id": "00000AAAAAFFFFF00000AAAAAF",
"receiving_num": null,
"purchase_transactions": [
{
"purchase_trans_id": 71001,
"purchase_id": 70001,
"product_id": 30006,
"ordered": "10",
"num_units": "120",
"fob": "12.50",
"deposit_cost": "0.00",
"ext_price": "1500.00",
"expiration_date": "2026-01-01T00:00:00.000Z",
"code_date": "2025-01-15T00:00:00.000Z",
"batch_code": "BCH-001",
"laid_in_cost": "13.00",
"pallet_tag": null,
"cases": "10",
"weight": "120.00",
"time_updated": "2025-03-02T10:00:00.000Z"
}
]
},
{
"purchase_id": null,
"receive_date": null,
"shipment_num": null,
"shipment_id": null,
"to_location_id": 40002,
"to_location_name": null,
"vendor_name": null,
"vendor_id": 50002,
"post_date": null,
"status": "InProgress",
"po_num": "PO-2026-00000",
"freight": "0.00",
"tax": "0.00",
"other_cost": "0.00",
"total": "0.00",
"total_cases": "30",
"public_pdf_link": null,
"purchase_pdf_url_api_key_id": null,
"memo": "Acme Brands - Submitted via KSS Live by Jamie Example",
"last_edit_time": null,
"last_calc_time": null,
"time_updated": "2026-08-13T15:04:05.000Z",
"invoice_date": null,
"due_date": null,
"terms": null,
"term_id": null,
"time_created": "2026-08-13T15:04:05.000Z",
"purchase_global_id": "11111BBBBBEEEEE11111BBBBBE",
"receiving_num": null,
"purchase_transactions": [
{
"id": 71101,
"product_id": 30008,
"num_units": 288,
"batch_code": "B-1042",
"expiration_date": "2027-01-15",
"needs_activation": false,
"activation_task_id": null
}
]
}
],
"page": 1,
"page_size": 50,
"has_next_page": false
}
Creates a purchase order. By default the purchase is created as an editable draft; pass submit=true as a query parameter to create and submit it in a single call. The response carries the new purchase's global ID, and a Location header pointing at the purchase, which is how you address it for later reads, edits, and submission. Not accessible to Customer API keys.
| Name | Type | Required | Description |
|---|---|---|---|
|
Idempotency-Key
header
|
string | optional |
Optional for POST and PATCH requests. Send a unique key for each logical mutation so an exact retry can replay the original response instead of running the operation again.
|
|
Prefer
header
|
string | optional |
Use return=minimal to omit the response body; return=representation is the default. Use handling=lenient to ignore unrecognized fields where supported.
|
|
submit
query
|
boolean | optional |
When true, creates the purchase directly in Submitted status after validating it is ready to submit.
|
| Field | Type | Required | Description |
|---|---|---|---|
|
vendor_id
body
|
number | required |
The vendor the purchase order is placed with.
|
|
to_location_id
body
|
number | required |
The location receiving the purchase.
|
|
supplier_id
body
|
number | required |
The supplier the purchase is for. Must be one of your authorized suppliers.
|
|
supplier_name
body
|
string | optional |
Supplier name used in the purchase memo. Falls back to the vendor's name when omitted.
|
|
delivery_date
body
|
date | required |
Requested delivery date as YYYY-MM-DD.
|
|
po_num
body
|
string | required |
Your purchase order number. Must be unique for the vendor.
|
|
purchase_transactions
body
|
object[] | optional | |
|
created_by_user_id
body
|
number | optional |
Honored for Employee keys only; other keys use the key's own user.
|
{
"vendor_id": 50002,
"to_location_id": 40002,
"supplier_id": 20001,
"supplier_name": "Acme Brands",
"delivery_date": "2026-08-20",
"po_num": "PO-2026-00000",
"purchase_transactions": [
{
"product_id": 30008,
"cases": 12,
"batch_code": "B-1042",
"expiration_date": "2027-01-15"
},
{
"product_id": 30009,
"units": 48
}
]
}
| Field | Description |
|---|---|
purchase_global_id |
The purchase's permanent identifier. Use it to read, edit, submit, or delete the purchase. |
purchase_id |
The numeric purchase ID. Null until the purchase has been processed. |
status |
InProgress for an editable draft, Submitted once it is queued for processing. |
total_cases |
Total case count across all line items. |
| Status | Description |
|---|---|
| 201 | Created. If Prefer: return=minimal is sent, the Location header is returned with no response body. |
| 400 | Validation failed. |
| 401 | The request is missing a valid API key. |
| 403 | The API key is not authorized to access this resource. |
| 409 | The request conflicts with an existing purchase or idempotency key. |
| 413 | The purchase body is too large. |
| 415 | The request content type is not supported. |
| 422 | The request is well-formed but could not be accepted. |
| 429 | Rate limit exceeded. |
| 500 | Unexpected server error. |
| Name | Type | Description |
|---|---|---|
Location |
string | Canonical URL for the created purchase, addressed by purchase_global_id. |
Idempotency-Replayed |
string | true when this response was replayed from a previous request with the same Idempotency-Key. |
Preference-Applied |
string | Echoes honored Prefer tokens, such as return=minimal or handling=lenient. |
{
"data": {
"purchase_id": null,
"receive_date": null,
"shipment_num": null,
"shipment_id": null,
"to_location_id": 40002,
"to_location_name": null,
"vendor_name": null,
"vendor_id": 50002,
"post_date": null,
"status": "InProgress",
"po_num": "PO-2026-00000",
"freight": "0.00",
"tax": "0.00",
"other_cost": "0.00",
"total": "0.00",
"total_cases": "30",
"public_pdf_link": null,
"purchase_pdf_url_api_key_id": null,
"memo": "Acme Brands - Submitted via KSS Live by Jamie Example",
"last_edit_time": null,
"last_calc_time": null,
"time_updated": "2026-08-13T15:04:05.000Z",
"invoice_date": null,
"due_date": null,
"terms": null,
"term_id": null,
"time_created": "2026-08-13T15:04:05.000Z",
"purchase_global_id": "11111BBBBBEEEEE11111BBBBBE",
"receiving_num": null,
"purchase_transactions": [
{
"id": 71101,
"product_id": 30008,
"num_units": 288,
"batch_code": "B-1042",
"expiration_date": "2027-01-15",
"needs_activation": false,
"activation_task_id": null
}
]
}
}
Validates a purchase order without creating anything, returning whether it is acceptable and its computed totals. Validates against the rules that apply at submit time: the purchase must have at least one line item, every quantity must be positive, and batch codes must be present where the vendor requires them, and po_num must be unique for the vendor. Not accessible to Customer API keys.
| Name | Type | Required | Description |
|---|---|---|---|
|
Prefer
header
|
string | optional |
Use return=minimal to omit the response body; return=representation is the default. Use handling=lenient to ignore unrecognized fields where supported.
|
| Field | Type | Required | Description |
|---|---|---|---|
|
vendor_id
body
|
number | required |
The vendor the purchase order is placed with.
|
|
to_location_id
body
|
number | required |
The location receiving the purchase.
|
|
supplier_id
body
|
number | required |
The supplier the purchase is for. Must be one of your authorized suppliers.
|
|
supplier_name
body
|
string | optional |
Supplier name used in the purchase memo. Falls back to the vendor's name when omitted.
|
|
delivery_date
body
|
date | required |
Requested delivery date as YYYY-MM-DD.
|
|
po_num
body
|
string | required |
Your purchase order number. Must be unique for the vendor.
|
|
purchase_transactions
body
|
object[] | optional | |
|
created_by_user_id
body
|
number | optional |
Honored for Employee keys only; other keys use the key's own user.
|
{
"vendor_id": 50002,
"to_location_id": 40002,
"supplier_id": 20001,
"supplier_name": "Acme Brands",
"delivery_date": "2026-08-20",
"po_num": "PO-2026-00000",
"purchase_transactions": [
{
"product_id": 30008,
"cases": 12,
"batch_code": "B-1042",
"expiration_date": "2027-01-15"
},
{
"product_id": 30009,
"units": 48
}
]
}
| Field | Description |
|---|---|
valid |
True when the purchase passed every validation check. |
total_cases |
Total case count derived across all line items. |
| Status | Description |
|---|---|
| 200 | Validation result. |
| 400 | Validation failed. |
| 401 | The request is missing a valid API key. |
| 403 | The API key is not authorized to access this resource. |
| 409 | The purchase conflicts with an existing purchase. |
| 413 | The request body is too large. |
| 415 | The request content type is not supported. |
| 422 | The request is well-formed but could not be accepted. |
| 429 | Rate limit exceeded. |
| 500 | Unexpected server error. |
{
"data": {
"valid": true,
"total_cases": 30
}
}
Returns a single purchase order. purchase_transactions are included by default; send include_transactions=false to return the purchase header only. Accepts either the purchase's global ID or its numeric purchase ID. The global ID resolves for the whole life of the purchase, from draft through processing and beyond, so the URL returned when a purchase is created never stops working. The numeric purchase ID resolves once the purchase has been processed and assigned one.
| Name | Type | Required | Description |
|---|---|---|---|
|
purchaseID
path
|
string | required |
The purchase's global ID or its numeric purchase ID.
|
|
include_transactions
query
|
boolean | optional |
Line items are included by default. Send false to omit purchase_transactions.
|
| Status | Description |
|---|---|
| 200 | OK |
| 400 | The request is missing required input or contains invalid input. |
| 401 | The request is missing a valid API key. |
| 403 | The API key is not authorized to access this resource. |
| 404 | The requested resource was not found. |
| 429 | Rate limit exceeded. |
| 500 | Unexpected server error. |
{
"data": {
"purchase_id": 70001,
"receive_date": "2026-06-02T06:00:00.000Z",
"shipment_num": "Example PO 70001",
"shipment_id": 72001,
"to_location_id": 40001,
"to_location_name": "Alameda",
"vendor_name": "EXAMPLE DISTRIBUTORS LLC (Example Location)",
"vendor_id": 50001,
"post_date": "2026-06-02T06:00:00.000Z",
"status": "Verified",
"po_num": "Example PO 70001",
"freight": "0.00",
"tax": "0.00",
"other_cost": "0.00",
"total": "35726.38",
"total_cases": "184.00",
"public_pdf_link": null,
"purchase_pdf_url_api_key_id": 94001,
"memo": null,
"last_edit_time": null,
"last_calc_time": "2026-06-04T10:20:14.000Z",
"time_updated": "2026-08-21T18:06:57.930Z",
"invoice_date": "2026-06-01T06:00:00.000Z",
"due_date": "2026-08-30T06:00:00.000Z",
"terms": "Driver Accounts",
"term_id": 80002,
"time_created": "2026-06-15T22:09:09.921Z",
"purchase_global_id": "00000AAAAAFFFFF00000AAAAAF",
"receiving_num": null,
"purchase_transactions": [
{
"purchase_trans_id": 71001,
"purchase_id": 70001,
"product_id": 30006,
"ordered": "10",
"num_units": "120",
"fob": "12.50",
"deposit_cost": "0.00",
"ext_price": "1500.00",
"expiration_date": "2026-01-01T00:00:00.000Z",
"code_date": "2025-01-15T00:00:00.000Z",
"batch_code": "BCH-001",
"laid_in_cost": "13.00",
"pallet_tag": null,
"cases": "10",
"weight": "120.00",
"time_updated": "2025-03-02T10:00:00.000Z"
}
]
}
}
Updates a draft purchase order. Send only the fields you want to change; anything you omit is left as it is. Only drafts can be edited: a purchase that has already been submitted returns 409. Not accessible to Customer API keys.
| Name | Type | Required | Description |
|---|---|---|---|
|
Idempotency-Key
header
|
string | optional |
Optional for POST and PATCH requests. Send a unique key for each logical mutation so an exact retry can replay the original response instead of running the operation again.
|
|
purchaseGlobalID
path
|
string | required |
The global ID of the draft purchase to update.
|
|
Prefer
header
|
string | optional |
Use return=minimal to omit the response body; return=representation is the default. Use handling=lenient to ignore unrecognized fields where supported.
|
| Field | Type | Required | Description |
|---|---|---|---|
|
vendor_id
body
|
number | optional |
The vendor the purchase order is placed with.
|
|
to_location_id
body
|
number | optional |
The location receiving the purchase.
|
|
supplier_id
body
|
number | optional |
The supplier the purchase is for. Must be one of your authorized suppliers.
|
|
supplier_name
body
|
string | optional |
Supplier name used in the purchase memo. Falls back to the vendor's name when omitted.
|
|
delivery_date
body
|
date | optional |
Requested delivery date as YYYY-MM-DD.
|
|
po_num
body
|
string | optional |
Your purchase order number. Must remain unique for the vendor.
|
|
purchase_transactions
body
|
object[] | optional |
When present, replaces all line items on the draft purchase.
|
{
"delivery_date": "2026-08-22",
"po_num": "PO-2026-00000-REV2",
"purchase_transactions": [
{
"product_id": 30008,
"cases": 15,
"batch_code": "B-1042",
"expiration_date": "2027-01-15"
}
]
}
| Status | Description |
|---|---|
| 200 | Updated draft purchase with the updated representation. |
| 204 | Updated draft purchase. Returned when Prefer: return=minimal is sent. |
| 400 | Validation failed. |
| 401 | The request is missing a valid API key. |
| 403 | The API key is not authorized to access this resource. |
| 404 | Purchase not found. |
| 409 | The purchase cannot be edited in its current state. |
| 413 | The request body is too large. |
| 415 | Unsupported request content type. |
| 422 | The request is well-formed but could not be accepted. |
| 429 | Rate limit exceeded. |
| 500 | Unexpected server error. |
| Name | Type | Description |
|---|---|---|
Idempotency-Replayed |
string | true when this response was replayed from a previous request with the same Idempotency-Key. |
Preference-Applied |
string | Echoes any honored Prefer tokens, such as return=minimal or handling=lenient. |
{
"data": {
"purchase_id": null,
"receive_date": null,
"shipment_num": null,
"shipment_id": null,
"to_location_id": 40002,
"to_location_name": null,
"vendor_name": null,
"vendor_id": 50002,
"post_date": null,
"status": "InProgress",
"po_num": "PO-2026-00000-REV2",
"freight": "0.00",
"tax": "0.00",
"other_cost": "0.00",
"total": "0.00",
"total_cases": "45",
"public_pdf_link": null,
"purchase_pdf_url_api_key_id": null,
"memo": "Acme Brands - Submitted via KSS Live by Jamie Example",
"last_edit_time": null,
"last_calc_time": null,
"time_updated": "2026-08-13T16:20:11.000Z",
"invoice_date": null,
"due_date": null,
"terms": null,
"term_id": null,
"time_created": "2026-08-13T15:04:05.000Z",
"purchase_global_id": "11111BBBBBEEEEE11111BBBBBE",
"receiving_num": null,
"purchase_transactions": [
{
"id": 71101,
"product_id": 30008,
"num_units": 288,
"batch_code": "B-1042",
"expiration_date": "2027-01-15",
"needs_activation": false,
"activation_task_id": null
}
]
}
}
Deletes a draft purchase order. Responds 204 with no body. Only drafts can be deleted: a purchase that has already been submitted returns 409, and a purchase that does not exist or has already been deleted returns 404. Not accessible to Customer API keys.
| Name | Type | Required | Description |
|---|---|---|---|
|
purchaseGlobalID
path
|
string | required |
The global ID of the draft purchase to delete.
|
| Status | Description |
|---|---|
| 204 | No Content |
| 400 | The request is missing required input or contains invalid input. |
| 401 | The request is missing a valid API key. |
| 403 | The API key is not authorized to access this resource. |
| 404 | Purchase not found. |
| 409 | The purchase cannot be deleted in its current state. |
| 429 | Rate limit exceeded. |
| 500 | Unexpected server error. |
Submits a draft purchase order for processing. The stored purchase is re-checked in full at this point, so a draft that was valid when it was saved can still be refused. A purchase that has already been submitted returns 409. Processing happens in the background and the response does not wait for it to finish; read the purchase to follow its status. Not accessible to Customer API keys.
| Name | Type | Required | Description |
|---|---|---|---|
|
Idempotency-Key
header
|
string | optional |
Optional for POST and PATCH requests. Send a unique key for each logical mutation so an exact retry can replay the original response instead of running the operation again.
|
|
purchaseGlobalID
path
|
string | required |
The global ID of the draft purchase to submit.
|
|
Prefer
header
|
string | optional |
Use return=minimal to omit the response body; return=representation is the default. Use handling=lenient to ignore unrecognized fields where supported.
|
| Status | Description |
|---|---|
| 200 | Submitted purchase with the updated representation. |
| 204 | Submitted purchase. Returned when Prefer: return=minimal is sent. |
| 400 | The request is missing required input or contains invalid input. |
| 401 | The request is missing a valid API key. |
| 403 | The API key is not authorized to access this resource. |
| 404 | Purchase not found. |
| 409 | The purchase cannot be submitted in its current state. |
| 422 | The purchase cannot be submitted as it stands. Everything stored on the draft is re-checked: the vendor must still be active, a delivery date must be set (DELIVERY_DATE_REQUIRED) and be one the vendor allows, there must be at least one line item, every line item must be a valid product for the vendor with a positive quantity, and batch codes must be present where the vendor requires them. Every problem found is listed in field_errors. |
| 429 | Rate limit exceeded. |
| 500 | Unexpected server error. |
| Name | Type | Description |
|---|---|---|
Idempotency-Replayed |
string | true when this response was replayed from a previous request with the same Idempotency-Key. |
Preference-Applied |
string | Echoes any honored Prefer tokens, such as return=minimal or handling=lenient. |
{
"data": {
"purchase_id": null,
"receive_date": null,
"shipment_num": null,
"shipment_id": null,
"to_location_id": 40002,
"to_location_name": null,
"vendor_name": null,
"vendor_id": 50002,
"post_date": null,
"status": "Submitted",
"po_num": "PO-2026-00000",
"freight": "0.00",
"tax": "0.00",
"other_cost": "0.00",
"total": "0.00",
"total_cases": "30",
"public_pdf_link": null,
"purchase_pdf_url_api_key_id": null,
"memo": "Acme Brands - Submitted via KSS Live by Jamie Example",
"last_edit_time": null,
"last_calc_time": null,
"time_updated": "2026-08-13T17:02:44.000Z",
"invoice_date": null,
"due_date": null,
"terms": null,
"term_id": null,
"time_created": "2026-08-13T15:04:05.000Z",
"purchase_global_id": "11111BBBBBEEEEE11111BBBBBE",
"receiving_num": null,
"purchase_transactions": [
{
"id": 71101,
"product_id": 30008,
"num_units": 288,
"batch_code": "B-1042",
"expiration_date": "2027-01-15",
"needs_activation": false,
"activation_task_id": null
}
]
}
}
Returns the scope of the API key making the request and its current rate-limit status. The request itself counts toward the limit, and the response is never cached, so the numbers always reflect the moment you asked. Useful for checking which suppliers, customers, and states a key can act for before debugging a 403.
| Field | Description |
|---|---|
api_key_id |
The numeric identifier of the API key making the request. |
type |
The kind of access the key has: Supplier, Customer, or Employee. |
environment |
The environment the key is issued for: Production, Test, or Development. |
user_id |
The numeric identifier of the user the key belongs to. |
supplier_ids |
The suppliers the key can act for. Null means the key is not restricted to particular suppliers. |
customer_ids |
The customers the key can act for. Null means the key is not restricted to particular customers. |
states |
The states the key can access. |
rate_limit |
The key's request allowance over a rolling 60-minute window. Null when rate-limit status is not available for the request. |
rate_limit.limit |
The maximum number of requests the key may make in a rolling 60-minute window. |
rate_limit.remaining |
The number of requests left in the current window, after counting this request. |
rate_limit.reset_seconds |
The number of seconds until the oldest request counted in the window ages out and frees up an allowance. |
| Status | Description |
|---|---|
| 200 | OK |
| 400 | The request is missing required input or contains invalid input. |
| 401 | The request is missing a valid API key. |
| 403 | The API key is not authorized to access this resource. |
| 429 | Rate limit exceeded. |
| 500 | Unexpected server error. |
{
"data": {
"api_key_id": 94001,
"type": "Supplier",
"environment": "Production",
"user_id": 10042,
"supplier_ids": [
20001,
20002
],
"customer_ids": null,
"states": [
"CA",
"NJ"
],
"rate_limit": {
"limit": 1000,
"remaining": 962,
"reset_seconds": 2718
}
}
}
Getting Started: Place and manage an order
An order starts as a draft, is submitted for processing, and becomes a posted order with an invoice. Its order ID follows it through that journey.
1. Build the order
Choose the customer, add products and quantities, and set the requested delivery date. You can also add a PO number, a delivery note, and buyer email addresses.
Save a draft while you're working. A draft can be incomplete, and you can return to it later.
2. Review it
Read the draft to see current prices, availability, and totals. Fix any product or quantity issues before submitting. You can also check whether the order is ready without saving changes.
Some brands or payment terms require separate invoices. The preview shows how the order will split, and submission returns the resulting orders to track.
3. Submit when ready
Submit the saved draft to queue it for processing. The API checks it again using the latest information, so prices or availability may have changed since your review.
If you already have all the details, you can create and submit in one request. A separate draft is optional.
4. Follow its progress
Read the order using its order ID. It moves from queued to processing, then becomes a posted order with an invoice. Once posted, you can follow its delivery status.
If processing is rejected, read the message to understand what happened. An accepted submission still needs time to finish processing and appear as a posted order.
Make a change
Draft edits are saved directly. After an order is posted, check which actions are available. If editing is allowed, save your changes, review them, and submit them. The posted order is updated after those changes finish processing.
You can discard saved changes before submitting them. When changing products, send the complete list you want to keep; the new list replaces the old one.
Delete or cancel
Delete a draft you no longer need. For a posted order, check whether cancellation is available, then request it and follow its progress until confirmed.
Submit or discard any pending edits before cancelling. An order still processing its initial submission cannot be deleted or cancelled.
Lists orders, newest first. By default, returns submitted and posted orders without lines. Use stages=draft for drafts and include_lines=true for line items.
| Name | Type | Required | Description |
|---|---|---|---|
|
order_ids
query
|
string | optional |
Comma-separated order IDs or invoice IDs; both can be mixed.
|
|
customer_ids
query
|
string | optional |
Comma-separated list of customer IDs to filter by.
|
|
states
query
|
string | optional |
Comma-separated customer state abbreviations.
|
|
stages
query
|
string | optional |
Comma-separated draft, submitted, posted. Default: submitted,posted; invoice_statuses restricts to posted. A statuses filter can also select drafts.
|
|
statuses
query
|
string | optional |
Comma-separated statuses; see status in Response Fields. Without stages, searches all stages. Deleted drafts require statuses=deleted.
|
|
invoice_statuses
query
|
string | optional |
Posted orders only. Comma-separated: 1 New, 2 Locked for Routing, 3 Loaded, 4 Returned, 5 Balanced, 7 Verified.
|
|
start_date
query
|
date | optional |
Delivery date on or after YYYY-MM-DD. Empty is ignored; invalid dates return 400.
|
|
end_date
query
|
date | optional |
Delivery date on or before YYYY-MM-DD. Empty is ignored; invalid dates return 400.
|
|
has_pending_changes
query
|
boolean | optional |
true: posted orders with pending edits or cancellation. false: orders with neither. Omit for both.
|
|
include_lines
query
|
boolean | optional |
Lines are omitted by default on lists. Send true to include lines on each order.
|
|
page
query
|
number | optional |
Page number to retrieve. Defaults to 1.
|
|
page_size
query
|
number | optional |
Number of rows to return per page. Defaults to 50; maximum is 500.
|
Supports Page and PageSize query parameters. Default page size: 50. Maximum page size: 500 — larger values are reduced to 500.
| Field | Description |
|---|---|
id |
Permanent 26-character order ID, valid across all stages. If null on a posted order, use invoice_id. |
invoice_id |
Numeric invoice ID, also accepted in order URLs. Null until posted. |
invoice_num |
The invoice number shown to the customer. Null until the order is posted. |
stage |
draft: editable, not submitted. submitted: queued or processing. posted: processed, with an invoice. |
status |
Draft: draft, deleted. Submitted: queued → importing → accepted (awaiting posting), or rejected (see processing.message). Posted: accepted (new), on_the_way, delivered, cancelled. |
invoice_status |
Numeric invoice status, the same values as Statuses in v1 invoices: 1 New, 2 Locked for Routing, 3 Loaded, 4 Returned, 5 Balanced, 7 Verified. Null until the order is posted. |
invoice_status_name |
The name of invoice_status. Null until the order is posted. |
is_credit |
True for a credit order. Credit orders cannot be edited. |
version |
Changes when the order or pending change changes. Also returned as ETag; use for If-Match or validation's version. |
customer_id |
The customer the order is placed for. |
customer_name |
The customer's name. |
state |
The customer's state. |
po_num |
The customer's purchase order number. |
po_date |
The PO date recorded on the posted order. Null until the order is posted. |
memo |
Free-text note on the order. |
delivery_date |
Requested delivery date as YYYY-MM-DD. On a posted order this is the order's delivery date. |
emails |
Buyer email addresses that receive order communications. |
term_id |
Payment term ID recorded on the posted order. Null before posting. |
credit_term_id |
The payment terms the order is on. On an unsubmitted order this is the terms it would be created with. Use GET /orders/creditTerms to look terms up. |
credit_term |
The name of credit_term_id. |
split_invoice_id |
Set when the order belongs to a separate invoice series, which a product can require. |
due_date |
When payment is due. Null until the order is posted. |
invoice_total |
Invoice total as a string with 2 decimals. Null until the order is posted. open_debit, open_credit, and open_balance are the outstanding amounts in the same format. |
pdf_url |
Link to the invoice PDF when one is available. |
ar_note |
Accounts receivable note on the invoice, when one exists. |
submitted_by_user_id |
The user who submitted the order. submitted_by_user_name and submitted_by_user_email identify them on posted orders; built_by_user_id, built_by_user_name, and built_by_user_email identify the user who built it. |
time_created |
When the order was created. |
time_updated |
When the order was last updated. |
totals |
Money uses strings with 2 decimals. units/cases count requested quantities. Drafts include shipped_units, ext_price (shippable total), and requested_ext_price (all requested units). full_price is before discounts; discount is the total discount. Keys limited to certain suppliers see totals for their suppliers' lines only, except on unsubmitted drafts they can edit. |
lines |
Line items. Omitted by default on lists; included by default on single-order reads. Drafts include credit_term_id, accept_backorder, minimum_applied (raised to product minimum), and issues. Posted lines include invoice_trans_id. Keys limited to certain suppliers see only their suppliers' lines, except on unsubmitted drafts they can edit. |
lines[].units |
Requested units on draft/posted orders; shippable units during submission processing. backorder_units counts unavailable units; shipped_units counts units that will or did ship. |
lines[].unit_price |
Per-unit price; full_price and discount are also per unit. ext_price = unit_price × shipped_units; requested_ext_price = unit_price × requested units. |
lines[].promotion_id |
Manual promotion to send back when editing. On posted lines, set only when promotion_overridden is true; applied_promotion_id identifies the pricing promotion, including automatic promotions. |
lines[].batch_code |
Batch the posted line was picked from. Null until assigned. |
lines[].coa_url |
Batch certificate of analysis URL. Null until attached. |
lines[].issues |
Submission blockers with code and detail: OUT_OF_STOCK, PRODUCT_INACTIVE, PRODUCT_NOT_AUTHORIZED, PRODUCT_NOT_FOUND, PROMOTION_UNAVAILABLE, PRICE_NOT_FOUND. Drafts can be saved with issues; submission returns 422. |
groups |
Draft split preview on single-order reads/writes. Separate invoice requirements or payment terms can split an order. Each group has group_key, order_number, credit terms, totals, line_indexes, and split_reason (SEPARATE_INVOICE_REQUIRED, CUSTOM_TERMS, SPLIT_INVOICE, DIFFERENT_TERMS; null for the first group). |
order_overrides |
PO number and memo overrides for individual orders an order splits into, keyed by group_key. Present on unsubmitted orders. |
pending_import_rows |
Rows from an imported file that could not be turned into line items. Each entry has a row_number. An order with pending rows cannot be submitted until they are resolved or discarded. |
pending_change |
Posted-order edit preview with the proposed header, lines, groups, and totals. Keys limited to certain suppliers see only their suppliers' lines, groups, and totals. state: draft → submitted → processing → applied (awaiting sync), or rejected (see message). editor and updated_by identify the first and last editors by user_id/name. |
cancellation |
Posted cancellation request: state, requested_at, requested_by_user_id, message. state: pending → applied (awaiting sync) → completed, or rejected (see message). |
processing |
Present on submitted orders: attempts is how many times processing has been tried, message the latest processing message, and updated_at when it last changed. |
related_orders |
Orders this order is connected to. parent_id and parent_invoice_id identify the order it was split from, and split_children lists the orders split from it, each with an id and invoice_id. |
allowed_actions |
Available actions: update, submit, discard_changes, cancel, delete, change_terms, set_promotions. Each has allowed; blocked actions include reason and detail matching write-error codes. Check these before offering an action. |
warnings |
Present on write responses when the request succeeded with caveats. Each warning has a field, code, and detail, and line warnings carry an index and product_id. Codes are QUANTITY_ADJUSTED, DELIVERY_DATE_WILL_BE_ADJUSTED, CUSTOMER_ON_HOLD, and ORIGINAL_INVOICE_WILL_BE_CANCELLED. |
resulting_orders |
Present on responses to submitting. See the field definitions on POST /orders/{order_id}/submit. |
| Status | Description |
|---|---|
| 200 | OK |
| 400 | Invalid filter (SCHEMA_VIOLATION) or order ID (ORDER_IDENTIFIER_INVALID). IDs must be 26-character order IDs or positive invoice IDs. |
| 401 | The request is missing a valid API key. |
| 403 | Key cannot read orders: USER_INACTIVE (linked user deactivated) or ACTOR_SCOPE_MISMATCH (supplier key linked to a customer user). |
| 429 | Rate limit exceeded. |
| 500 | Unexpected server error. |
{
"data": [
{
"id": "01K9X7M2QH4T8VZ3BN6CRWD5EJ",
"invoice_id": null,
"invoice_num": null,
"stage": "submitted",
"status": "queued",
"invoice_status": null,
"invoice_status_name": null,
"is_credit": false,
"version": "v1.Hd7Fk2PzQw9XcN4rVyLm1TbS8gJeA6uOiYtCs0RnE3U",
"customer_id": 41001,
"customer_name": "Example Dispensary",
"state": "CA",
"po_num": "PO-1042",
"po_date": null,
"memo": "Deliver to the back dock",
"delivery_date": "2026-10-14",
"emails": [
"buyer@example.com"
],
"term_id": null,
"credit_term_id": 80003,
"credit_term": "Net 30",
"split_invoice_id": null,
"due_date": null,
"invoice_total": null,
"open_debit": null,
"open_credit": null,
"open_balance": null,
"pdf_url": null,
"ar_note": null,
"submitted_by_user_id": 10042,
"built_by_user_id": 10042,
"built_by_user_name": "Jamie Example",
"time_created": "2026-10-08T15:04:05.000Z",
"time_updated": "2026-10-08T15:20:31.000Z",
"totals": {
"units": 144,
"backorder_units": 24,
"cases": 12,
"full_price": "2076.00",
"discount": "120.00",
"ext_price": "1956.00"
},
"order_overrides": {},
"pending_import_rows": [],
"pending_change": null,
"cancellation": null,
"processing": {
"attempts": 0,
"message": "Queued by user 10042 via API",
"updated_at": "2026-10-08T15:20:31.000Z"
},
"related_orders": {
"parent_id": null,
"parent_invoice_id": null,
"split_children": []
},
"allowed_actions": {
"update": {
"allowed": false,
"reason": "ORDER_PROCESSING",
"detail": "This order is being processed and cannot be changed."
},
"submit": {
"allowed": false,
"reason": "ALREADY_SUBMITTED",
"detail": "This order has already been submitted."
},
"discard_changes": {
"allowed": false,
"reason": "REVISION_NOT_FOUND",
"detail": "This order has no pending changes."
},
"cancel": {
"allowed": false,
"reason": "ORDER_PROCESSING",
"detail": "This order is being processed and cannot be changed."
},
"delete": {
"allowed": false,
"reason": "ORDER_PROCESSING",
"detail": "This order is being processed and cannot be changed."
},
"change_terms": {
"allowed": false,
"reason": "ORDER_PROCESSING",
"detail": "This order is being processed and cannot be changed."
},
"set_promotions": {
"allowed": false,
"reason": "ORDER_PROCESSING",
"detail": "This order is being processed and cannot be changed."
}
}
},
{
"id": "01K9X7P5TD0A1MFY6S2GHQ8RNC",
"invoice_id": 90017,
"invoice_num": "INV-90017",
"stage": "posted",
"status": "accepted",
"invoice_status": 1,
"invoice_status_name": "New",
"is_credit": false,
"version": "v1.Zq4N8cXw1LmT6vYb0RhPjK3sDfG7uEaO9iWtSyCe2Bk",
"customer_id": 41001,
"customer_name": "Example Dispensary",
"state": "CA",
"po_num": "PO-1031",
"po_date": "2026-10-06T07:00:00.000Z",
"memo": null,
"delivery_date": "2026-10-14",
"emails": [
"buyer@example.com"
],
"term_id": 70002,
"credit_term_id": 80003,
"credit_term": "Net 30",
"split_invoice_id": null,
"due_date": "2026-11-13T07:00:00.000Z",
"invoice_total": "1380.00",
"open_debit": "1380.00",
"open_credit": "0.00",
"open_balance": "1380.00",
"pdf_url": null,
"ar_note": null,
"submitted_by_user_id": 10042,
"submitted_by_user_name": "Jamie Example",
"submitted_by_user_email": "jamie@example.com",
"built_by_user_id": 10042,
"built_by_user_name": "Jamie Example",
"built_by_user_email": "jamie@example.com",
"time_created": "2026-10-06T16:12:44.000Z",
"time_updated": "2026-10-06T16:30:02.000Z",
"totals": {
"units": 120,
"cases": 10,
"full_price": "1500.00",
"discount": "120.00",
"ext_price": "1380.00"
},
"pending_change": null,
"cancellation": null,
"processing": null,
"related_orders": {
"parent_id": null,
"parent_invoice_id": null,
"split_children": []
},
"allowed_actions": {
"update": {
"allowed": true
},
"submit": {
"allowed": false,
"reason": "NO_PENDING_CHANGES",
"detail": "There are no pending changes to submit."
},
"discard_changes": {
"allowed": false,
"reason": "REVISION_NOT_FOUND",
"detail": "This order has no pending changes."
},
"cancel": {
"allowed": true
},
"delete": {
"allowed": true
},
"change_terms": {
"allowed": true
},
"set_promotions": {
"allowed": true
}
}
}
],
"page": 1,
"page_size": 50,
"has_next_page": false
}
Creates a draft (201). Add ?submit=true to create and submit in one request (202). Drafts may be incomplete; lines are validated and priced. Use the returned id for later requests. Requires an API key linked to an active user.
| Name | Type | Required | Description |
|---|---|---|---|
|
Idempotency-Key
header
|
string | optional |
Optional for POST and PATCH requests. Send a unique key for each logical mutation so an exact retry can replay the original response instead of running the operation again.
|
|
Prefer
header
|
string | optional |
Use return=minimal to omit the response body; return=representation is the default. Use handling=lenient to ignore unrecognized fields where supported.
|
|
submit
query
|
boolean | optional |
Create and submit atomically (202). If submission fails, nothing is saved.
|
| Field | Type | Required | Description |
|---|---|---|---|
|
customer_id
body
|
number | required |
The customer the order is placed for. Must be one you can act for.
|
|
po_num
body
|
string | optional |
PO number, up to 50 characters. Must be unique among the customer's orders from the last 7 days when validated or submitted. Splits append -1, -2, etc. unless overridden; no number is generated if omitted.
|
|
memo
body
|
string | optional |
Free-text note on the order, at most 500 characters.
|
|
delivery_date
body
|
date | optional |
Requested delivery date as YYYY-MM-DD with no time component. Required to submit.
|
|
emails
body
|
email[] | optional |
Up to 20 buyer email addresses for order communications. Replaces the existing list; null or [] clears it.
|
|
order_overrides
body
|
object | optional |
Optional po_num and memo per split order, keyed by groups[].group_key. Updates merge by key. Null removes a key or value; null for the whole field clears all overrides. An empty po_num suppresses the PO number.
|
|
lines
body
|
object[] | optional |
Up to 200 lines, each with units or cases. Replaces ALL existing lines when sent. Repeated product/promotion pairs are combined.
|
|
save_term_defaults
body
|
object | optional |
On submission, save selected custom payment terms as the customer's supplier defaults.
|
{
"customer_id": 41001,
"po_num": "PO-1042",
"memo": "Deliver to the back dock",
"delivery_date": "2026-10-14",
"emails": [
"buyer@example.com"
],
"lines": [
{
"product_id": 30008,
"cases": 10
},
{
"product_id": 30009,
"units": 48,
"accept_backorder": true
}
]
}
| Field | Description |
|---|---|
id |
The order's permanent 26-character order ID. Use it to read, edit, submit, or delete the order. |
version |
The order's current version, also returned as the ETag header. Send it back in If-Match to make later changes conditional. |
warnings |
Non-blocking notices: QUANTITY_ADJUSTED, CUSTOMER_ON_HOLD, DELIVERY_DATE_WILL_BE_ADJUSTED. |
resulting_orders |
Orders produced by submission. Separate invoice requirements or payment terms can split one order into several. |
resulting_orders[].id |
The resulting order's 26-character order ID. Null for an order whose ID is not confirmed yet; use invoice_id. |
resulting_orders[].invoice_id |
The invoice ID, when the order already has one. Null for new orders. |
resulting_orders[].role |
original is the submitted order itself, split is a new order created from it, revised is a posted order updated by a pending change, and cancelled is a posted order cancelled because none of its remaining lines belong on it. |
resulting_orders[].group_key |
Identifies the group of lines the order covers, the same value used in order_overrides. Null for cancelled. |
resulting_orders[].order_number |
Position of the order among the orders produced, starting at 1. Null for cancelled. |
resulting_orders[].status |
Always queued at submission time. Read the order to follow its progress. |
resulting_orders[].split_reason |
Why the order was created separately, with a code and a detail. Null for the original and revised orders. |
resulting_orders[].totals |
The order's totals, in the same shape as the totals field. Null for cancelled. |
| Status | Description |
|---|---|
| 201 | Created as an unsubmitted draft. If Prefer: return=minimal is sent, the Location and ETag headers are returned with no response body. |
| 202 | Created and submitted with submit=true. The order is queued for processing; resulting_orders lists the orders it produced. If Prefer: return=minimal is sent, the Location and ETag headers are returned with no response body. |
| 400 | Invalid request; see field_errors for each problem. Codes: SCHEMA_VIOLATION, ORDER_IDENTIFIER_INVALID, LINE_QUANTITY_AMBIGUOUS (send exactly one of units or cases), QUANTITY_INVALID, TOO_MANY_LINE_ITEMS, DUPLICATE_LINE_CONFLICT (conflicting terms/backorder settings on repeated lines), DELIVERY_DATE_NOT_CALENDAR, FIELD_TOO_LONG, BATCH_NOT_SUPPORTED (send one order, not an array). |
| 401 | The request is missing a valid API key. |
| 403 | Key cannot make this change. Codes: CUSTOMER_NOT_PERMITTED, NOT_PERMITTED, TERMS_NOT_PERMITTED (own brands only), OTHER_SUPPLIERS, MANUAL_PROMOTION (sales rep required), FEATURE_DISABLED, ORDER_ACTOR_REQUIRED (linked user required), USER_INACTIVE, ACTOR_SCOPE_MISMATCH (supplier key linked to a customer user). |
| 409 | Conflict: FIELD_IMMUTABLE (changed read-only field), or reused Idempotency-Key with a different request. With submit=true: DUPLICATE_PO_NUMBER (another recent order), DUPLICATE_PO_IN_ORDER (duplicate across splits), DUPLICATE_PO_IN_REVISION (duplicate across revised splits). |
| 413 | The order body is larger than 256 KB. An order may contain at most 200 lines. |
| 415 | The request content type is not supported. |
| 422 | Line or import-row validation failed; see field_errors. Codes: PRODUCT_NOT_FOUND (unavailable in the customer's state), PRODUCT_NOT_FOR_SUPPLIER, PROMOTION_NOT_PERMITTED, SAMPLE_NOT_PERMITTED (Customer keys cannot add or increase samples), NOT_CASE_MULTIPLE, BELOW_MIN_ORDER, PRODUCT_MISSING_UNITS_PER_CASE (send units), QUANTITY_INVALID, CREDIT_TERM_NOT_FOUND, TERM_NOT_OFFERED, PENDING_ROW_NOT_FOUND. With submit=true: Submission blocked; see field_errors. Codes: NO_LINE_ITEMS (cancel instead of removing all lines), DELIVERY_DATE_REQUIRED, DELIVERY_DATE_PAST_CUTOFF, PENDING_IMPORT_ROWS, OUT_OF_STOCK, PRODUCT_INACTIVE, PRODUCT_NOT_AUTHORIZED, PRODUCT_NOT_FOUND, PROMOTION_UNAVAILABLE, PRICE_NOT_FOUND, PRICING_UNAVAILABLE. Line errors include index and product_id. |
| 429 | Rate limit exceeded. |
| 500 | Unexpected server error. |
| Name | Type | Description |
|---|---|---|
Location |
string | Canonical URL for the created order, addressed by its order ID. |
ETag |
string | Quoted order version. Send it in If-Match to prevent overwriting a changed order. |
Idempotency-Replayed |
string | true when this response was replayed from a previous request with the same Idempotency-Key. |
Preference-Applied |
string | Echoes honored Prefer tokens, such as return=minimal or handling=lenient. |
| Name | Type | Description |
|---|---|---|
Location |
string | Canonical URL for the created order, addressed by its order ID. |
ETag |
string | Quoted order version. Send it in If-Match to prevent overwriting a changed order. |
Idempotency-Replayed |
string | true when this response was replayed from a previous request with the same Idempotency-Key. |
Preference-Applied |
string | Echoes honored Prefer tokens, such as return=minimal or handling=lenient. |
{
"data": {
"id": "01K9X7M2QH4T8VZ3BN6CRWD5EJ",
"invoice_id": null,
"invoice_num": null,
"stage": "draft",
"status": "draft",
"invoice_status": null,
"invoice_status_name": null,
"is_credit": false,
"version": "v1.8s3Yk0dQJ2mXw4Zr1pVb7NcLhT6aUeF9gOiSyKjRnM0",
"customer_id": 41001,
"customer_name": "Example Dispensary",
"state": "CA",
"po_num": "PO-1042",
"po_date": null,
"memo": "Deliver to the back dock",
"delivery_date": "2026-10-14",
"emails": [
"buyer@example.com"
],
"term_id": null,
"credit_term_id": 80003,
"credit_term": "Net 30",
"split_invoice_id": null,
"due_date": null,
"invoice_total": null,
"open_debit": null,
"open_credit": null,
"open_balance": null,
"pdf_url": null,
"ar_note": null,
"submitted_by_user_id": null,
"built_by_user_id": 10042,
"built_by_user_name": "Jamie Example",
"time_created": "2026-10-08T15:04:05.000Z",
"time_updated": "2026-10-08T15:04:05.000Z",
"totals": {
"units": 168,
"backorder_units": 24,
"shipped_units": 144,
"cases": 14,
"full_price": "2076.00",
"discount": "120.00",
"ext_price": "1956.00",
"requested_ext_price": "2532.00"
},
"order_overrides": {},
"pending_import_rows": [],
"pending_change": null,
"cancellation": null,
"processing": null,
"related_orders": {
"parent_id": null,
"parent_invoice_id": null,
"split_children": []
},
"allowed_actions": {
"update": {
"allowed": true
},
"submit": {
"allowed": true
},
"discard_changes": {
"allowed": false,
"reason": "REVISION_NOT_FOUND",
"detail": "Unsubmitted orders have no pending changes to discard."
},
"cancel": {
"allowed": false,
"reason": "NOT_SUBMITTED",
"detail": "This order has not been submitted. Delete it instead."
},
"delete": {
"allowed": true
},
"change_terms": {
"allowed": true
},
"set_promotions": {
"allowed": true
}
},
"lines": [
{
"product_id": 30008,
"product_name": "Acme Gummies 10pk",
"supplier_id": 20001,
"supplier_name": "Acme Brands",
"promotion_id": null,
"quantity_promotion_id": null,
"units": 120,
"backorder_units": 0,
"shipped_units": 120,
"cases": 10,
"unit_price": "11.50",
"full_price": "12.50",
"discount": "1.00",
"ext_price": "1380.00",
"requested_ext_price": "1380.00",
"credit_term_id": 80003,
"credit_term": "Net 30",
"term_overridden": false,
"accept_backorder": false,
"minimum_applied": false,
"exception": null,
"issues": []
},
{
"product_id": 30009,
"product_name": "Acme Chews 10pk",
"supplier_id": 20001,
"supplier_name": "Acme Brands",
"promotion_id": null,
"quantity_promotion_id": null,
"units": 48,
"backorder_units": 24,
"shipped_units": 24,
"cases": 4,
"unit_price": "24.00",
"full_price": "24.00",
"discount": "0.00",
"ext_price": "576.00",
"requested_ext_price": "1152.00",
"credit_term_id": 80003,
"credit_term": "Net 30",
"term_overridden": false,
"accept_backorder": true,
"minimum_applied": false,
"exception": "Backorder accepted",
"issues": []
}
],
"groups": [
{
"group_key": "shared:0:0",
"order_number": 1,
"split_invoice_id": null,
"credit_term_id": 80003,
"credit_term": "Net 30",
"term_overridden": false,
"split_reason": null,
"line_indexes": [
0,
1
],
"totals": {
"units": 168,
"backorder_units": 24,
"shipped_units": 144,
"cases": 14,
"full_price": "2076.00",
"discount": "120.00",
"ext_price": "1956.00",
"requested_ext_price": "2532.00"
}
}
]
}
}
Returns the payment terms that can be used as credit_term_id on an order line, ordered by term_id.
| Name | Type | Required | Description |
|---|---|---|---|
|
term_ids
query
|
string | optional |
Comma-separated list of term IDs to filter by.
|
|
page
query
|
number | optional |
Page number to retrieve. Defaults to 1.
|
|
page_size
query
|
number | optional |
Number of rows to return per page. Defaults to 50; maximum is 500.
|
Supports Page and PageSize query parameters. Default page size: 50. Maximum page size: 500 — larger values are reduced to 500.
| Field | Description |
|---|---|
term_id |
The payment term's ID, used as credit_term_id on order lines. |
term |
The payment term's name. |
days_of_credit |
How many days of credit the term allows. |
account_on_hold |
True when the term places the customer's account on hold. |
time_updated |
When the payment term was last updated. |
| Status | Description |
|---|---|
| 200 | OK |
| 400 | The request is missing required input or contains invalid input. |
| 401 | The request is missing a valid API key. |
| 403 | Key cannot read orders: USER_INACTIVE (linked user deactivated) or ACTOR_SCOPE_MISMATCH (supplier key linked to a customer user). |
| 429 | Rate limit exceeded. |
| 500 | Unexpected server error. |
{
"data": [
{
"term_id": 80003,
"term": "Net 30",
"days_of_credit": 30,
"account_on_hold": false,
"time_updated": "2026-01-15T18:00:00.000Z"
}
],
"page": 1,
"page_size": 50,
"has_next_page": false
}
Checks whether an order can be submitted, without saving it. Returns valid, errors, warnings, and a pricing/split preview. For an existing order, send order_id and the changes you would PATCH. An API key without a linked user can validate.
| Name | Type | Required | Description |
|---|---|---|---|
|
Prefer
header
|
string | optional |
Use return=minimal to omit the response body; return=representation is the default. Use handling=lenient to ignore unrecognized fields where supported.
|
| Field | Type | Required | Description |
|---|---|---|---|
|
order_id
body
|
string | optional |
Existing order ID or invoice ID to validate changes against. Omit for a new order and supply customer_id.
|
|
version
body
|
string | optional |
Last-read version. A stale version adds a VERSION_MISMATCH warning; validation uses the current order.
|
|
customer_id
body
|
number | optional |
Required when order_id is omitted.
|
|
po_num
body
|
string | optional |
PO number, up to 50 characters. Must be unique among the customer's orders from the last 7 days when validated or submitted. Splits append -1, -2, etc. unless overridden; no number is generated if omitted.
|
|
memo
body
|
string | optional |
Free-text note on the order, at most 500 characters.
|
|
delivery_date
body
|
date | optional |
Requested delivery date as YYYY-MM-DD with no time component. Required to submit.
|
|
emails
body
|
email[] | optional |
Up to 20 buyer email addresses for order communications. Replaces the existing list; null or [] clears it.
|
|
order_overrides
body
|
object | optional |
Optional po_num and memo per split order, keyed by groups[].group_key. Updates merge by key. Null removes a key or value; null for the whole field clears all overrides. An empty po_num suppresses the PO number.
|
|
lines
body
|
object[] | optional |
Up to 200 lines, each with units or cases. Replaces ALL existing lines when sent. Repeated product/promotion pairs are combined.
|
|
pending_import_rows
body
|
number[] | optional |
Unresolved import row numbers to KEEP; unlisted rows are discarded. Null or [] discards all. A row that is not pending returns 422.
|
{
"customer_id": 41001,
"po_num": "PO-1042",
"lines": [
{
"product_id": 30008,
"cases": 10
},
{
"product_id": 30009,
"units": 48,
"accept_backorder": true
}
]
}
| Field | Description |
|---|---|
valid |
True when errors is empty, so the order could be submitted as it stands. |
errors |
Problems that would block submission, each with a field, code, and detail. Line problems carry an index and product_id. |
warnings |
Caveats that do not block submission, in the same shape as errors. Codes: QUANTITY_ADJUSTED, DELIVERY_DATE_WILL_BE_ADJUSTED, CUSTOMER_ON_HOLD, ORIGINAL_INVOICE_WILL_BE_CANCELLED, and VERSION_MISMATCH, which is returned only here when the version you sent no longer matches the order. |
pricing_date |
The date the lines were priced for. Null when no lines could be priced. |
lines |
The priced lines, in the same shape as lines on an unsubmitted order, including issues. |
totals |
Totals across all lines, in the same shape as totals on an order. Null when no lines could be priced. For an existing order, keys limited to certain suppliers see lines, groups, totals, and line issues for their suppliers only, except on unsubmitted drafts they can edit. |
groups |
Split preview: group_key, order_number, is_original (stays on the existing order), split_reason, totals, line_indexes, po_num. PO numbers use overrides or the original with -1, -2, etc. for splits; null if none was supplied. |
cancels_original |
True when validating a change to a posted order in which none of the remaining lines belong on the original order, so submitting would cancel it and move the lines to new orders. |
| Status | Description |
|---|---|
| 200 | Validation result. |
| 400 | Invalid request; see field_errors for each problem. Codes: SCHEMA_VIOLATION, ORDER_IDENTIFIER_INVALID, LINE_QUANTITY_AMBIGUOUS (send exactly one of units or cases), QUANTITY_INVALID, TOO_MANY_LINE_ITEMS, DUPLICATE_LINE_CONFLICT (conflicting terms/backorder settings on repeated lines), DELIVERY_DATE_NOT_CALENDAR, FIELD_TOO_LONG, BATCH_NOT_SUPPORTED (send one order, not an array). |
| 401 | The request is missing a valid API key. |
| 403 | Key cannot make this change. Codes: CUSTOMER_NOT_PERMITTED, NOT_PERMITTED, TERMS_NOT_PERMITTED (own brands only), OTHER_SUPPLIERS, MANUAL_PROMOTION (sales rep required), FEATURE_DISABLED, ORDER_ACTOR_REQUIRED (linked user required), USER_INACTIVE, ACTOR_SCOPE_MISMATCH (supplier key linked to a customer user). |
| 404 | The order named by order_id does not exist or is outside what the key can access. Code: ORDER_NOT_FOUND. |
| 409 | The body cannot be validated against the order. Code: CUSTOMER_IMMUTABLE (customer_id differs from the customer on an existing order). |
| 413 | The request body is too large. |
| 415 | The request content type is not supported. |
| 422 | Line or import-row validation failed; see field_errors. Codes: PRODUCT_NOT_FOUND (unavailable in the customer's state), PRODUCT_NOT_FOR_SUPPLIER, PROMOTION_NOT_PERMITTED, SAMPLE_NOT_PERMITTED (Customer keys cannot add or increase samples), NOT_CASE_MULTIPLE, BELOW_MIN_ORDER, PRODUCT_MISSING_UNITS_PER_CASE (send units), QUANTITY_INVALID, CREDIT_TERM_NOT_FOUND, TERM_NOT_OFFERED, PENDING_ROW_NOT_FOUND. |
| 429 | Rate limit exceeded. |
| 500 | Unexpected server error. |
{
"data": {
"valid": false,
"errors": [
{
"field": "delivery_date",
"code": "DELIVERY_DATE_REQUIRED",
"detail": "A delivery date is required before the order can be submitted."
}
],
"warnings": [],
"pricing_date": "2026-10-08",
"lines": [
{
"product_id": 30008,
"product_name": "Acme Gummies 10pk",
"supplier_id": 20001,
"supplier_name": "Acme Brands",
"promotion_id": null,
"quantity_promotion_id": null,
"units": 120,
"backorder_units": 0,
"shipped_units": 120,
"cases": 10,
"unit_price": "11.50",
"full_price": "12.50",
"discount": "1.00",
"ext_price": "1380.00",
"requested_ext_price": "1380.00",
"credit_term_id": 80003,
"credit_term": "Net 30",
"term_overridden": false,
"accept_backorder": false,
"minimum_applied": false,
"exception": null,
"issues": []
},
{
"product_id": 30009,
"product_name": "Acme Chews 10pk",
"supplier_id": 20001,
"supplier_name": "Acme Brands",
"promotion_id": null,
"quantity_promotion_id": null,
"units": 48,
"backorder_units": 24,
"shipped_units": 24,
"cases": 4,
"unit_price": "24.00",
"full_price": "24.00",
"discount": "0.00",
"ext_price": "576.00",
"requested_ext_price": "1152.00",
"credit_term_id": 80003,
"credit_term": "Net 30",
"term_overridden": false,
"accept_backorder": true,
"minimum_applied": false,
"exception": "Backorder accepted",
"issues": []
}
],
"totals": {
"units": 168,
"backorder_units": 24,
"shipped_units": 144,
"cases": 14,
"full_price": "2076.00",
"discount": "120.00",
"ext_price": "1956.00",
"requested_ext_price": "2532.00"
},
"groups": [
{
"group_key": "shared:0:0",
"order_number": 1,
"split_invoice_id": null,
"credit_term_id": 80003,
"credit_term": "Net 30",
"term_overridden": false,
"split_reason": null,
"line_indexes": [
0,
1
],
"totals": {
"units": 168,
"backorder_units": 24,
"shipped_units": 144,
"cases": 14,
"full_price": "2076.00",
"discount": "120.00",
"ext_price": "1956.00",
"requested_ext_price": "2532.00"
},
"is_original": true,
"po_num": "PO-1042"
}
],
"cancels_original": false
}
}
Returns an order with lines and allowed_actions. Drafts are repriced on each read. The ETag holds the current version for If-Match; responses are not cached.
| Name | Type | Required | Description |
|---|---|---|---|
|
order_id
path
|
string | required |
26-character order ID (all stages) or numeric invoice ID (posted orders only).
|
|
include_lines
query
|
boolean | optional |
Lines are included by default. Send false to omit lines.
|
| Field | Description |
|---|---|
id |
Permanent 26-character order ID, valid across all stages. If null on a posted order, use invoice_id. |
invoice_id |
Numeric invoice ID, also accepted in order URLs. Null until posted. |
invoice_num |
The invoice number shown to the customer. Null until the order is posted. |
stage |
draft: editable, not submitted. submitted: queued or processing. posted: processed, with an invoice. |
status |
Draft: draft, deleted. Submitted: queued → importing → accepted (awaiting posting), or rejected (see processing.message). Posted: accepted (new), on_the_way, delivered, cancelled. |
invoice_status |
Numeric invoice status, the same values as Statuses in v1 invoices: 1 New, 2 Locked for Routing, 3 Loaded, 4 Returned, 5 Balanced, 7 Verified. Null until the order is posted. |
invoice_status_name |
The name of invoice_status. Null until the order is posted. |
is_credit |
True for a credit order. Credit orders cannot be edited. |
version |
Changes when the order or pending change changes. Also returned as ETag; use for If-Match or validation's version. |
customer_id |
The customer the order is placed for. |
customer_name |
The customer's name. |
state |
The customer's state. |
po_num |
The customer's purchase order number. |
po_date |
The PO date recorded on the posted order. Null until the order is posted. |
memo |
Free-text note on the order. |
delivery_date |
Requested delivery date as YYYY-MM-DD. On a posted order this is the order's delivery date. |
emails |
Buyer email addresses that receive order communications. |
term_id |
Payment term ID recorded on the posted order. Null before posting. |
credit_term_id |
The payment terms the order is on. On an unsubmitted order this is the terms it would be created with. Use GET /orders/creditTerms to look terms up. |
credit_term |
The name of credit_term_id. |
split_invoice_id |
Set when the order belongs to a separate invoice series, which a product can require. |
due_date |
When payment is due. Null until the order is posted. |
invoice_total |
Invoice total as a string with 2 decimals. Null until the order is posted. open_debit, open_credit, and open_balance are the outstanding amounts in the same format. |
pdf_url |
Link to the invoice PDF when one is available. |
ar_note |
Accounts receivable note on the invoice, when one exists. |
submitted_by_user_id |
The user who submitted the order. submitted_by_user_name and submitted_by_user_email identify them on posted orders; built_by_user_id, built_by_user_name, and built_by_user_email identify the user who built it. |
time_created |
When the order was created. |
time_updated |
When the order was last updated. |
totals |
Money uses strings with 2 decimals. units/cases count requested quantities. Drafts include shipped_units, ext_price (shippable total), and requested_ext_price (all requested units). full_price is before discounts; discount is the total discount. Keys limited to certain suppliers see totals for their suppliers' lines only, except on unsubmitted drafts they can edit. |
lines |
Line items. Omitted by default on lists; included by default on single-order reads. Drafts include credit_term_id, accept_backorder, minimum_applied (raised to product minimum), and issues. Posted lines include invoice_trans_id. Keys limited to certain suppliers see only their suppliers' lines, except on unsubmitted drafts they can edit. |
lines[].units |
Requested units on draft/posted orders; shippable units during submission processing. backorder_units counts unavailable units; shipped_units counts units that will or did ship. |
lines[].unit_price |
Per-unit price; full_price and discount are also per unit. ext_price = unit_price × shipped_units; requested_ext_price = unit_price × requested units. |
lines[].promotion_id |
Manual promotion to send back when editing. On posted lines, set only when promotion_overridden is true; applied_promotion_id identifies the pricing promotion, including automatic promotions. |
lines[].batch_code |
Batch the posted line was picked from. Null until assigned. |
lines[].coa_url |
Batch certificate of analysis URL. Null until attached. |
lines[].issues |
Submission blockers with code and detail: OUT_OF_STOCK, PRODUCT_INACTIVE, PRODUCT_NOT_AUTHORIZED, PRODUCT_NOT_FOUND, PROMOTION_UNAVAILABLE, PRICE_NOT_FOUND. Drafts can be saved with issues; submission returns 422. |
groups |
Draft split preview on single-order reads/writes. Separate invoice requirements or payment terms can split an order. Each group has group_key, order_number, credit terms, totals, line_indexes, and split_reason (SEPARATE_INVOICE_REQUIRED, CUSTOM_TERMS, SPLIT_INVOICE, DIFFERENT_TERMS; null for the first group). |
order_overrides |
PO number and memo overrides for individual orders an order splits into, keyed by group_key. Present on unsubmitted orders. |
pending_import_rows |
Rows from an imported file that could not be turned into line items. Each entry has a row_number. An order with pending rows cannot be submitted until they are resolved or discarded. |
pending_change |
Posted-order edit preview with the proposed header, lines, groups, and totals. Keys limited to certain suppliers see only their suppliers' lines, groups, and totals. state: draft → submitted → processing → applied (awaiting sync), or rejected (see message). editor and updated_by identify the first and last editors by user_id/name. |
cancellation |
Posted cancellation request: state, requested_at, requested_by_user_id, message. state: pending → applied (awaiting sync) → completed, or rejected (see message). |
processing |
Present on submitted orders: attempts is how many times processing has been tried, message the latest processing message, and updated_at when it last changed. |
related_orders |
Orders this order is connected to. parent_id and parent_invoice_id identify the order it was split from, and split_children lists the orders split from it, each with an id and invoice_id. |
allowed_actions |
Available actions: update, submit, discard_changes, cancel, delete, change_terms, set_promotions. Each has allowed; blocked actions include reason and detail matching write-error codes. Check these before offering an action. |
warnings |
Present on write responses when the request succeeded with caveats. Each warning has a field, code, and detail, and line warnings carry an index and product_id. Codes are QUANTITY_ADJUSTED, DELIVERY_DATE_WILL_BE_ADJUSTED, CUSTOMER_ON_HOLD, and ORIGINAL_INVOICE_WILL_BE_CANCELLED. |
resulting_orders |
Present on responses to submitting. See the field definitions on POST /orders/{order_id}/submit. |
| Status | Description |
|---|---|
| 200 | OK |
| 400 | Invalid filter (SCHEMA_VIOLATION) or order ID (ORDER_IDENTIFIER_INVALID). IDs must be 26-character order IDs or positive invoice IDs. |
| 401 | The request is missing a valid API key. |
| 403 | Key cannot read orders: USER_INACTIVE (linked user deactivated) or ACTOR_SCOPE_MISMATCH (supplier key linked to a customer user). |
| 404 | ORDER_NOT_FOUND: order does not exist or is outside the key's access. |
| 429 | Rate limit exceeded. |
| 500 | Unexpected server error. |
| Name | Type | Description |
|---|---|---|
ETag |
string | Quoted order version. Send it in If-Match to prevent overwriting a changed order. |
Cache-Control |
string | Always no-store. |
{
"data": {
"id": "01K9X7P5TD0A1MFY6S2GHQ8RNC",
"invoice_id": 90017,
"invoice_num": "INV-90017",
"stage": "posted",
"status": "accepted",
"invoice_status": 1,
"invoice_status_name": "New",
"is_credit": false,
"version": "v1.Zq4N8cXw1LmT6vYb0RhPjK3sDfG7uEaO9iWtSyCe2Bk",
"customer_id": 41001,
"customer_name": "Example Dispensary",
"state": "CA",
"po_num": "PO-1031",
"po_date": "2026-10-06T07:00:00.000Z",
"memo": null,
"delivery_date": "2026-10-14",
"emails": [
"buyer@example.com"
],
"term_id": 70002,
"credit_term_id": 80003,
"credit_term": "Net 30",
"split_invoice_id": null,
"due_date": "2026-11-13T07:00:00.000Z",
"invoice_total": "1380.00",
"open_debit": "1380.00",
"open_credit": "0.00",
"open_balance": "1380.00",
"pdf_url": null,
"ar_note": null,
"submitted_by_user_id": 10042,
"submitted_by_user_name": "Jamie Example",
"submitted_by_user_email": "jamie@example.com",
"built_by_user_id": 10042,
"built_by_user_name": "Jamie Example",
"built_by_user_email": "jamie@example.com",
"time_created": "2026-10-06T16:12:44.000Z",
"time_updated": "2026-10-06T16:30:02.000Z",
"totals": {
"units": 120,
"cases": 10,
"full_price": "1500.00",
"discount": "120.00",
"ext_price": "1380.00"
},
"pending_change": null,
"cancellation": null,
"processing": null,
"related_orders": {
"parent_id": null,
"parent_invoice_id": null,
"split_children": []
},
"allowed_actions": {
"update": {
"allowed": true
},
"submit": {
"allowed": false,
"reason": "NO_PENDING_CHANGES",
"detail": "There are no pending changes to submit."
},
"discard_changes": {
"allowed": false,
"reason": "REVISION_NOT_FOUND",
"detail": "This order has no pending changes."
},
"cancel": {
"allowed": true
},
"delete": {
"allowed": true
},
"change_terms": {
"allowed": true
},
"set_promotions": {
"allowed": true
}
},
"lines": [
{
"invoice_trans_id": 91001,
"product_id": 30008,
"product_name": "Acme Gummies 10pk",
"supplier_id": 20001,
"promotion_id": null,
"applied_promotion_id": null,
"promotion_overridden": false,
"units": 120,
"shipped_units": 120,
"backorder_units": 0,
"cases": 10,
"unit_price": "11.50",
"full_price": "12.50",
"discount": "1.00",
"ext_price": "1380.00",
"batch_code": "26191BBB01",
"coa_url": "https://cdn.example.com/coa/26191BBB01.pdf"
}
]
}
}
Edits a draft directly, or saves pending_change on a posted order for later submission. Send only changed fields; omitted fields stay unchanged and null clears nullable fields. Add ?submit=true to save and submit atomically (202). Requires an API key linked to an active user.
| Name | Type | Required | Description |
|---|---|---|---|
|
Idempotency-Key
header
|
string | optional |
Optional for POST and PATCH requests. Send a unique key for each logical mutation so an exact retry can replay the original response instead of running the operation again.
|
|
order_id
path
|
string | required |
26-character order ID (all stages) or numeric invoice ID (posted orders only).
|
|
Prefer
header
|
string | optional |
Use return=minimal to omit the response body; return=representation is the default. Use handling=lenient to ignore unrecognized fields where supported.
|
|
If-Match
header
|
string | optional |
Last-read version or ETag; stale values return 412 VERSION_MISMATCH. Required when another user last edited the pending change; omitting it returns 409 REVISION_DRAFT_IN_USE.
|
|
submit
query
|
boolean | optional |
Save and submit the draft or pending change atomically (202). If submission fails, nothing is saved.
|
| Field | Type | Required | Description |
|---|---|---|---|
|
customer_id
body
|
number | optional |
May be repeated, but the customer on an order cannot be changed once it has been submitted.
|
|
po_num
body
|
string | optional |
PO number, up to 50 characters. Must be unique among the customer's orders from the last 7 days when validated or submitted. Splits append -1, -2, etc. unless overridden; no number is generated if omitted.
|
|
memo
body
|
string | optional |
Free-text note on the order, at most 500 characters.
|
|
delivery_date
body
|
date | optional |
Requested delivery date as YYYY-MM-DD with no time component. Required to submit.
|
|
emails
body
|
email[] | optional |
Up to 20 buyer email addresses for order communications. Replaces the existing list; null or [] clears it.
|
|
order_overrides
body
|
object | optional |
Optional po_num and memo per split order, keyed by groups[].group_key. Updates merge by key. Null removes a key or value; null for the whole field clears all overrides. An empty po_num suppresses the PO number.
|
|
lines
body
|
object[] | optional |
Up to 200 lines, each with units or cases. Replaces ALL existing lines when sent. Repeated product/promotion pairs are combined.
|
|
pending_import_rows
body
|
number[] | optional |
Unresolved import row numbers to KEEP; unlisted rows are discarded. Null or [] discards all. A row that is not pending returns 422.
|
|
save_term_defaults
body
|
object | optional |
On submission, save selected custom payment terms as the customer's supplier defaults.
|
{
"delivery_date": "2026-10-15",
"lines": [
{
"product_id": 30008,
"units": 144
}
]
}
| Field | Description |
|---|---|
pending_change |
Proposed posted-order changes. Updated on each edit; the posted order changes only after submission and processing. |
version |
The order's new version, also returned as the ETag header. It changes after every edit. |
warnings |
Present when the update was saved with caveats, such as a QUANTITY_ADJUSTED line under Prefer: handling=lenient. |
resulting_orders |
Orders produced by submission. Separate invoice requirements or payment terms can split one order into several. |
resulting_orders[].id |
The resulting order's 26-character order ID. Null for an order whose ID is not confirmed yet; use invoice_id. |
resulting_orders[].invoice_id |
The invoice ID, when the order already has one. Null for new orders. |
resulting_orders[].role |
original is the submitted order itself, split is a new order created from it, revised is a posted order updated by a pending change, and cancelled is a posted order cancelled because none of its remaining lines belong on it. |
resulting_orders[].group_key |
Identifies the group of lines the order covers, the same value used in order_overrides. Null for cancelled. |
resulting_orders[].order_number |
Position of the order among the orders produced, starting at 1. Null for cancelled. |
resulting_orders[].status |
Always queued at submission time. Read the order to follow its progress. |
resulting_orders[].split_reason |
Why the order was created separately, with a code and a detail. Null for the original and revised orders. |
resulting_orders[].totals |
The order's totals, in the same shape as the totals field. Null for cancelled. |
| Status | Description |
|---|---|
| 200 | Updated. On a posted order the pending change is shown in pending_change. If Prefer: return=minimal is sent, the response is 204 with no body. |
| 202 | Updated and submitted with submit=true. resulting_orders lists the orders it produced. If Prefer: return=minimal is sent, the ETag header is returned with no response body. |
| 204 | Updated. Returned when Prefer: return=minimal is sent. |
| 400 | Invalid request; see field_errors for each problem. Codes: SCHEMA_VIOLATION, ORDER_IDENTIFIER_INVALID, LINE_QUANTITY_AMBIGUOUS (send exactly one of units or cases), QUANTITY_INVALID, TOO_MANY_LINE_ITEMS, DUPLICATE_LINE_CONFLICT (conflicting terms/backorder settings on repeated lines), DELIVERY_DATE_NOT_CALENDAR, FIELD_TOO_LONG, BATCH_NOT_SUPPORTED (send one order, not an array). |
| 401 | The request is missing a valid API key. |
| 403 | Key cannot make this change. Codes: CUSTOMER_NOT_PERMITTED, NOT_PERMITTED, TERMS_NOT_PERMITTED (own brands only), OTHER_SUPPLIERS, MANUAL_PROMOTION (sales rep required), FEATURE_DISABLED, ORDER_ACTOR_REQUIRED (linked user required), USER_INACTIVE, ACTOR_SCOPE_MISMATCH (supplier key linked to a customer user). |
| 404 | ORDER_NOT_FOUND: order does not exist or is outside the key's access. |
| 409 | State conflict. Drafts: ALREADY_SUBMITTED, NOT_EDITABLE_IN_STATE, ORDER_DELETED. Posted: NOT_NEW, LOCKED, PAYMENT_APPLIED, CREDIT_INVOICE, HAS_DOCUMENTS, PAST_CUTOFF, NO_DELIVERY_DATES, REVISION_PENDING (change processing), REVISION_SYNCING (change applied, awaiting sync), REVISION_DRAFT_IN_USE (send If-Match), REVISION_REJECTED (discard rejected cancellation), INVOICE_CHANGED_SINCE_DRAFT (discard stale edits and restart), CUSTOMER_IMMUTABLE, NO_EDITABLE_LINES. Read-only fields: FIELD_IMMUTABLE. With submit=true: DUPLICATE_PO_NUMBER (another recent order), DUPLICATE_PO_IN_ORDER (duplicate across splits), DUPLICATE_PO_IN_REVISION (duplicate across revised splits). |
| 412 | If-Match no longer matches the order's version. Code: VERSION_MISMATCH. Fetch the order again and retry with its new version. |
| 413 | The order body is larger than 256 KB. An order may contain at most 200 lines. |
| 415 | Unsupported request content type. |
| 422 | Line or import-row validation failed; see field_errors. Codes: PRODUCT_NOT_FOUND (unavailable in the customer's state), PRODUCT_NOT_FOR_SUPPLIER, PROMOTION_NOT_PERMITTED, SAMPLE_NOT_PERMITTED (Customer keys cannot add or increase samples), NOT_CASE_MULTIPLE, BELOW_MIN_ORDER, PRODUCT_MISSING_UNITS_PER_CASE (send units), QUANTITY_INVALID, CREDIT_TERM_NOT_FOUND, TERM_NOT_OFFERED, PENDING_ROW_NOT_FOUND. With submit=true: Submission blocked; see field_errors. Codes: NO_LINE_ITEMS (cancel instead of removing all lines), DELIVERY_DATE_REQUIRED, DELIVERY_DATE_PAST_CUTOFF, PENDING_IMPORT_ROWS, OUT_OF_STOCK, PRODUCT_INACTIVE, PRODUCT_NOT_AUTHORIZED, PRODUCT_NOT_FOUND, PROMOTION_UNAVAILABLE, PRICE_NOT_FOUND, PRICING_UNAVAILABLE. Line errors include index and product_id. |
| 429 | Rate limit exceeded. |
| 500 | Unexpected server error. |
| Name | Type | Description |
|---|---|---|
ETag |
string | Quoted order version. Send it in If-Match to prevent overwriting a changed order. |
Idempotency-Replayed |
string | true when this response was replayed from a previous request with the same Idempotency-Key. |
Preference-Applied |
string | Echoes honored Prefer tokens, such as return=minimal or handling=lenient. |
| Name | Type | Description |
|---|---|---|
ETag |
string | Quoted order version. Send it in If-Match to prevent overwriting a changed order. |
Idempotency-Replayed |
string | true when this response was replayed from a previous request with the same Idempotency-Key. |
Preference-Applied |
string | Echoes honored Prefer tokens, such as return=minimal or handling=lenient. |
{
"data": {
"id": "01K9X7P5TD0A1MFY6S2GHQ8RNC",
"invoice_id": 90017,
"invoice_num": "INV-90017",
"stage": "posted",
"status": "accepted",
"invoice_status": 1,
"invoice_status_name": "New",
"is_credit": false,
"version": "v1.Tn5Vb2KcXq8LzW1mRyHj7PdSf4GgA9uEoIeYtCs6Mw0",
"customer_id": 41001,
"customer_name": "Example Dispensary",
"state": "CA",
"po_num": "PO-1031",
"po_date": "2026-10-06T07:00:00.000Z",
"memo": null,
"delivery_date": "2026-10-14",
"emails": [
"buyer@example.com"
],
"term_id": 70002,
"credit_term_id": 80003,
"credit_term": "Net 30",
"split_invoice_id": null,
"due_date": "2026-11-13T07:00:00.000Z",
"invoice_total": "1380.00",
"open_debit": "1380.00",
"open_credit": "0.00",
"open_balance": "1380.00",
"pdf_url": null,
"ar_note": null,
"submitted_by_user_id": 10042,
"submitted_by_user_name": "Jamie Example",
"submitted_by_user_email": "jamie@example.com",
"built_by_user_id": 10042,
"built_by_user_name": "Jamie Example",
"built_by_user_email": "jamie@example.com",
"time_created": "2026-10-06T16:12:44.000Z",
"time_updated": "2026-10-08T16:02:10.000Z",
"totals": {
"units": 120,
"cases": 10,
"full_price": "1500.00",
"discount": "120.00",
"ext_price": "1380.00"
},
"pending_change": {
"state": "draft",
"updated_at": "2026-10-08T16:02:10.000Z",
"editor": {
"user_id": 10042,
"name": "Jamie Example"
},
"updated_by": {
"user_id": 10042,
"name": "Jamie Example"
},
"message": null,
"delivery_date": "2026-10-15",
"po_num": "PO-1031",
"memo": null,
"emails": [
"buyer@example.com"
],
"order_overrides": {},
"totals": {
"units": 144,
"backorder_units": 0,
"shipped_units": 144,
"cases": 12,
"full_price": "1800.00",
"discount": "144.00",
"ext_price": "1656.00",
"requested_ext_price": "1656.00"
},
"lines": [
{
"product_id": 30008,
"product_name": "Acme Gummies 10pk",
"supplier_id": 20001,
"supplier_name": "Acme Brands",
"promotion_id": null,
"quantity_promotion_id": null,
"units": 144,
"backorder_units": 0,
"shipped_units": 144,
"cases": 12,
"unit_price": "11.50",
"full_price": "12.50",
"discount": "1.00",
"ext_price": "1656.00",
"requested_ext_price": "1656.00",
"credit_term_id": 80003,
"credit_term": "Net 30",
"term_overridden": false,
"accept_backorder": false,
"minimum_applied": false,
"exception": null,
"issues": []
}
],
"groups": [
{
"group_key": "shared:0:0",
"order_number": 1,
"split_invoice_id": null,
"credit_term_id": 80003,
"credit_term": "Net 30",
"term_overridden": false,
"split_reason": null,
"line_indexes": [
0
],
"totals": {
"units": 144,
"backorder_units": 0,
"shipped_units": 144,
"cases": 12,
"full_price": "1800.00",
"discount": "144.00",
"ext_price": "1656.00",
"requested_ext_price": "1656.00"
}
}
]
},
"cancellation": null,
"processing": null,
"related_orders": {
"parent_id": null,
"parent_invoice_id": null,
"split_children": []
},
"allowed_actions": {
"update": {
"allowed": true
},
"submit": {
"allowed": true
},
"discard_changes": {
"allowed": true
},
"cancel": {
"allowed": false,
"reason": "REVISION_DRAFT_EXISTS",
"detail": "Discard or submit the pending changes before cancelling the order."
},
"delete": {
"allowed": false,
"reason": "REVISION_DRAFT_EXISTS",
"detail": "Discard or submit the pending changes before cancelling the order."
},
"change_terms": {
"allowed": true
},
"set_promotions": {
"allowed": true
}
},
"lines": [
{
"invoice_trans_id": 91001,
"product_id": 30008,
"product_name": "Acme Gummies 10pk",
"supplier_id": 20001,
"promotion_id": null,
"applied_promotion_id": null,
"promotion_overridden": false,
"units": 120,
"shipped_units": 120,
"backorder_units": 0,
"cases": 10,
"unit_price": "11.50",
"full_price": "12.50",
"discount": "1.00",
"ext_price": "1380.00",
"batch_code": "26191BBB01",
"coa_url": "https://cdn.example.com/coa/26191BBB01.pdf"
}
]
}
}
Deletes a draft (204) or requests cancellation of a posted order (202). Repeated requests return the current outcome. Submitted orders still being processed cannot be deleted or cancelled. Requires an API key linked to an active user.
| Name | Type | Required | Description |
|---|---|---|---|
|
order_id
path
|
string | required |
26-character order ID (all stages) or numeric invoice ID (posted orders only).
|
|
Prefer
header
|
string | optional |
Use return=minimal to omit the response body; return=representation is the default. Use handling=lenient to ignore unrecognized fields where supported.
|
|
If-Match
header
|
string | optional |
Last-read version or ETag; stale values return 412 VERSION_MISMATCH.
|
| Field | Description |
|---|---|
cancellation.state |
pending while the cancellation is queued or being processed, applied once it has been accepted and is waiting to sync, completed once the order is cancelled, and rejected if it was refused. |
cancellation.requested_at |
When cancellation was requested. |
cancellation.requested_by_user_id |
The user who requested cancellation. |
cancellation.message |
Why a cancellation was rejected. Null otherwise. |
| Status | Description |
|---|---|
| 202 | Cancellation of a posted order was requested, or is already under way or complete. If Prefer: return=minimal is sent, the ETag header is returned with no response body. |
| 204 | The draft was deleted, or had already been deleted. |
| 400 | Invalid filter (SCHEMA_VIOLATION) or order ID (ORDER_IDENTIFIER_INVALID). IDs must be 26-character order IDs or positive invoice IDs. |
| 401 | The request is missing a valid API key. |
| 403 | Key cannot make this change. Codes: CUSTOMER_NOT_PERMITTED, NOT_PERMITTED, TERMS_NOT_PERMITTED (own brands only), OTHER_SUPPLIERS, MANUAL_PROMOTION (sales rep required), FEATURE_DISABLED, ORDER_ACTOR_REQUIRED (linked user required), USER_INACTIVE, ACTOR_SCOPE_MISMATCH (supplier key linked to a customer user). |
| 404 | ORDER_NOT_FOUND: order does not exist or is outside the key's access. |
| 409 | State conflict: REVISION_DRAFT_EXISTS (submit/discard edits first), NOTHING_TO_CANCEL, ORDER_PROCESSING, INVOICE_NOT_SYNCED (wait for posting), NOT_EDITABLE_IN_STATE. Posted orders also use: NOT_NEW, LOCKED, PAYMENT_APPLIED, CREDIT_INVOICE, HAS_DOCUMENTS, PAST_CUTOFF, NO_DELIVERY_DATES, REVISION_PENDING (change processing), REVISION_SYNCING (change applied, awaiting sync). |
| 412 | If-Match no longer matches the order's version. Code: VERSION_MISMATCH. Fetch the order again and retry with its new version. |
| 429 | Rate limit exceeded. |
| 500 | Unexpected server error. |
| Name | Type | Description |
|---|---|---|
ETag |
string | Quoted order version. Send it in If-Match to prevent overwriting a changed order. |
Preference-Applied |
string | Echoes honored Prefer tokens, such as return=minimal or handling=lenient. |
{
"data": {
"id": "01K9X7P5TD0A1MFY6S2GHQ8RNC",
"invoice_id": 90017,
"invoice_num": "INV-90017",
"stage": "posted",
"status": "accepted",
"invoice_status": 1,
"invoice_status_name": "New",
"is_credit": false,
"version": "v1.Pw3Lc8XnQ1VzK6mRtHj2YdSf5GgA7uEoIbTeCs9Nk4",
"customer_id": 41001,
"customer_name": "Example Dispensary",
"state": "CA",
"po_num": "PO-1031",
"po_date": "2026-10-06T07:00:00.000Z",
"memo": null,
"delivery_date": "2026-10-14",
"emails": [
"buyer@example.com"
],
"term_id": 70002,
"credit_term_id": 80003,
"credit_term": "Net 30",
"split_invoice_id": null,
"due_date": "2026-11-13T07:00:00.000Z",
"invoice_total": "1380.00",
"open_debit": "1380.00",
"open_credit": "0.00",
"open_balance": "1380.00",
"pdf_url": null,
"ar_note": null,
"submitted_by_user_id": 10042,
"submitted_by_user_name": "Jamie Example",
"submitted_by_user_email": "jamie@example.com",
"built_by_user_id": 10042,
"built_by_user_name": "Jamie Example",
"built_by_user_email": "jamie@example.com",
"time_created": "2026-10-06T16:12:44.000Z",
"time_updated": "2026-10-06T16:30:02.000Z",
"totals": {
"units": 120,
"cases": 10,
"full_price": "1500.00",
"discount": "120.00",
"ext_price": "1380.00"
},
"pending_change": null,
"cancellation": {
"state": "pending",
"requested_at": "2026-10-08T16:40:12.000Z",
"requested_by_user_id": 10042,
"message": null
},
"processing": null,
"related_orders": {
"parent_id": null,
"parent_invoice_id": null,
"split_children": []
},
"allowed_actions": {
"update": {
"allowed": false,
"reason": "REVISION_PENDING",
"detail": "This update is being processed. Try again after it is confirmed."
},
"submit": {
"allowed": false,
"reason": "REVISION_PENDING",
"detail": "This update is being processed. Try again after it is confirmed."
},
"discard_changes": {
"allowed": false,
"reason": "REVISION_NOT_FOUND",
"detail": "This order has no pending changes."
},
"cancel": {
"allowed": false,
"reason": "CANCELLATION_REQUESTED",
"detail": "Cancellation has already been requested."
},
"delete": {
"allowed": false,
"reason": "CANCELLATION_REQUESTED",
"detail": "Cancellation has already been requested."
},
"change_terms": {
"allowed": false,
"reason": "TERMS_NOT_PERMITTED",
"detail": "You cannot change payment terms on this order."
},
"set_promotions": {
"allowed": false,
"reason": "PROMOTION_NOT_PERMITTED",
"detail": "You cannot apply manual promotions."
}
}
}
}
Submits a draft or a posted order's pending change. Revalidates the order and returns 202 when queued; processing continues in the background. Poll GET /orders/{order_id} for progress. Requires an API key linked to an active user.
| Name | Type | Required | Description |
|---|---|---|---|
|
Idempotency-Key
header
|
string | optional |
Optional for POST and PATCH requests. Send a unique key for each logical mutation so an exact retry can replay the original response instead of running the operation again.
|
|
order_id
path
|
string | required |
26-character order ID (all stages) or numeric invoice ID (posted orders only).
|
|
Prefer
header
|
string | optional |
Use return=minimal to omit the response body; return=representation is the default. Use handling=lenient to ignore unrecognized fields where supported.
|
|
If-Match
header
|
string | optional |
Last-read version or ETag; stale values return 412 VERSION_MISMATCH. Required when another user last edited the pending change; omitting it returns 409 REVISION_DRAFT_IN_USE.
|
| Field | Type | Required | Description |
|---|---|---|---|
|
save_term_defaults
body
|
object | optional |
On submission, save selected custom payment terms as the customer's supplier defaults.
|
{
"save_term_defaults": {
"group_keys": [
"shared:0:80003"
],
"supplier_ids": [
20001
]
}
}
| Field | Description |
|---|---|
stage |
submitted in the response to a draft, with status queued. In the response to a posted order's pending change the order stays posted and pending_change.state is submitted. |
warnings |
Present when the order was accepted with caveats: CUSTOMER_ON_HOLD, DELIVERY_DATE_WILL_BE_ADJUSTED, or ORIGINAL_INVOICE_WILL_BE_CANCELLED. |
resulting_orders |
Orders produced by submission. Separate invoice requirements or payment terms can split one order into several. |
resulting_orders[].id |
The resulting order's 26-character order ID. Null for an order whose ID is not confirmed yet; use invoice_id. |
resulting_orders[].invoice_id |
The invoice ID, when the order already has one. Null for new orders. |
resulting_orders[].role |
original is the submitted order itself, split is a new order created from it, revised is a posted order updated by a pending change, and cancelled is a posted order cancelled because none of its remaining lines belong on it. |
resulting_orders[].group_key |
Identifies the group of lines the order covers, the same value used in order_overrides. Null for cancelled. |
resulting_orders[].order_number |
Position of the order among the orders produced, starting at 1. Null for cancelled. |
resulting_orders[].status |
Always queued at submission time. Read the order to follow its progress. |
resulting_orders[].split_reason |
Why the order was created separately, with a code and a detail. Null for the original and revised orders. |
resulting_orders[].totals |
The order's totals, in the same shape as the totals field. Null for cancelled. |
| Status | Description |
|---|---|
| 202 | Accepted for processing. If Prefer: return=minimal is sent, the ETag header is returned with no response body. |
| 400 | Invalid request; see field_errors for each problem. Codes: SCHEMA_VIOLATION, ORDER_IDENTIFIER_INVALID, LINE_QUANTITY_AMBIGUOUS (send exactly one of units or cases), QUANTITY_INVALID, TOO_MANY_LINE_ITEMS, DUPLICATE_LINE_CONFLICT (conflicting terms/backorder settings on repeated lines), DELIVERY_DATE_NOT_CALENDAR, FIELD_TOO_LONG, BATCH_NOT_SUPPORTED (send one order, not an array). |
| 401 | The request is missing a valid API key. |
| 403 | Key cannot make this change. Codes: CUSTOMER_NOT_PERMITTED, NOT_PERMITTED, TERMS_NOT_PERMITTED (own brands only), OTHER_SUPPLIERS, MANUAL_PROMOTION (sales rep required), FEATURE_DISABLED, ORDER_ACTOR_REQUIRED (linked user required), USER_INACTIVE, ACTOR_SCOPE_MISMATCH (supplier key linked to a customer user). |
| 404 | ORDER_NOT_FOUND: order does not exist or is outside the key's access. |
| 409 | State conflict: ALREADY_SUBMITTED, ORDER_DELETED, NO_PENDING_CHANGES, REVISION_REJECTED (edit to reopen), INVOICE_CHANGED_SINCE_DRAFT (discard stale edits and restart), REVISION_DRAFT_IN_USE (send If-Match), DUPLICATE_PO_NUMBER (another recent order), DUPLICATE_PO_IN_ORDER (duplicate across splits), DUPLICATE_PO_IN_REVISION (duplicate across revised splits). Posted orders also use: NOT_NEW, LOCKED, PAYMENT_APPLIED, CREDIT_INVOICE, HAS_DOCUMENTS, PAST_CUTOFF, NO_DELIVERY_DATES, REVISION_PENDING (change processing), REVISION_SYNCING (change applied, awaiting sync). |
| 412 | If-Match no longer matches the order's version. Code: VERSION_MISMATCH. Fetch the order again and retry with its new version. |
| 413 | The request body is too large. |
| 415 | The request content type is not supported. |
| 422 | Submission blocked; see field_errors. Codes: NO_LINE_ITEMS (cancel instead of removing all lines), DELIVERY_DATE_REQUIRED, DELIVERY_DATE_PAST_CUTOFF, PENDING_IMPORT_ROWS, OUT_OF_STOCK, PRODUCT_INACTIVE, PRODUCT_NOT_AUTHORIZED, PRODUCT_NOT_FOUND, PROMOTION_UNAVAILABLE, PRICE_NOT_FOUND, PRICING_UNAVAILABLE. Line errors include index and product_id. |
| 429 | Rate limit exceeded. |
| 500 | Unexpected server error. |
| Name | Type | Description |
|---|---|---|
ETag |
string | Quoted order version. Send it in If-Match to prevent overwriting a changed order. |
Idempotency-Replayed |
string | true when this response was replayed from a previous request with the same Idempotency-Key. |
Preference-Applied |
string | Echoes honored Prefer tokens, such as return=minimal or handling=lenient. |
{
"data": {
"id": "01K9X7M2QH4T8VZ3BN6CRWD5EJ",
"invoice_id": null,
"invoice_num": null,
"stage": "submitted",
"status": "queued",
"invoice_status": null,
"invoice_status_name": null,
"is_credit": false,
"version": "v1.Hd7Fk2PzQw9XcN4rVyLm1TbS8gJeA6uOiYtCs0RnE3U",
"customer_id": 41001,
"customer_name": "Example Dispensary",
"state": "CA",
"po_num": "PO-1042",
"po_date": null,
"memo": "Deliver to the back dock",
"delivery_date": "2026-10-14",
"emails": [
"buyer@example.com"
],
"term_id": null,
"credit_term_id": 80003,
"credit_term": "Net 30",
"split_invoice_id": null,
"due_date": null,
"invoice_total": null,
"open_debit": null,
"open_credit": null,
"open_balance": null,
"pdf_url": null,
"ar_note": null,
"submitted_by_user_id": 10042,
"built_by_user_id": 10042,
"built_by_user_name": "Jamie Example",
"time_created": "2026-10-08T15:04:05.000Z",
"time_updated": "2026-10-08T15:20:31.000Z",
"totals": {
"units": 144,
"backorder_units": 24,
"cases": 12,
"full_price": "2076.00",
"discount": "120.00",
"ext_price": "1956.00"
},
"order_overrides": {},
"pending_import_rows": [],
"pending_change": null,
"cancellation": null,
"processing": {
"attempts": 0,
"message": "Queued by user 10042 via API",
"updated_at": "2026-10-08T15:20:31.000Z"
},
"related_orders": {
"parent_id": null,
"parent_invoice_id": null,
"split_children": []
},
"allowed_actions": {
"update": {
"allowed": false,
"reason": "ORDER_PROCESSING",
"detail": "This order is being processed and cannot be changed."
},
"submit": {
"allowed": false,
"reason": "ALREADY_SUBMITTED",
"detail": "This order has already been submitted."
},
"discard_changes": {
"allowed": false,
"reason": "REVISION_NOT_FOUND",
"detail": "This order has no pending changes."
},
"cancel": {
"allowed": false,
"reason": "ORDER_PROCESSING",
"detail": "This order is being processed and cannot be changed."
},
"delete": {
"allowed": false,
"reason": "ORDER_PROCESSING",
"detail": "This order is being processed and cannot be changed."
},
"change_terms": {
"allowed": false,
"reason": "ORDER_PROCESSING",
"detail": "This order is being processed and cannot be changed."
},
"set_promotions": {
"allowed": false,
"reason": "ORDER_PROCESSING",
"detail": "This order is being processed and cannot be changed."
}
},
"lines": [
{
"product_id": 30008,
"product_name": "Acme Gummies 10pk",
"supplier_id": 20001,
"promotion_id": null,
"quantity_promotion_id": null,
"units": 120,
"backorder_units": 0,
"cases": 10,
"unit_price": "11.50",
"discount": "1.00",
"ext_price": "1380.00",
"credit_term_id": null,
"term_overridden": false,
"exception": null
},
{
"product_id": 30009,
"product_name": "Acme Chews 10pk",
"supplier_id": 20001,
"promotion_id": null,
"quantity_promotion_id": null,
"units": 24,
"backorder_units": 24,
"cases": 2,
"unit_price": "24.00",
"discount": "0.00",
"ext_price": "576.00",
"credit_term_id": null,
"term_overridden": false,
"exception": "Backorder accepted"
}
],
"resulting_orders": [
{
"id": "01K9X7M2QH4T8VZ3BN6CRWD5EJ",
"invoice_id": null,
"role": "original",
"group_key": "shared:0:0",
"order_number": 1,
"status": "queued",
"split_reason": null,
"totals": {
"units": 168,
"backorder_units": 24,
"shipped_units": 144,
"cases": 14,
"full_price": "2076.00",
"discount": "120.00",
"ext_price": "1956.00",
"requested_ext_price": "2532.00"
}
}
]
}
}
Discards unsubmitted edits or a rejected cancellation on a posted order (204). The posted order stays unchanged. Already-submitted changes cannot be discarded. For drafts, use DELETE /orders/{order_id}. Requires an API key linked to an active user.
| Name | Type | Required | Description |
|---|---|---|---|
|
order_id
path
|
string | required |
The posted order's 26-character order ID or its numeric invoice ID.
|
|
If-Match
header
|
string | optional |
Last-read version or ETag; stale values return 412 VERSION_MISMATCH. Required when another user last edited the pending change; omitting it returns 409 REVISION_DRAFT_IN_USE.
|
| Status | Description |
|---|---|
| 204 | The pending changes were discarded, or there were none. |
| 400 | Invalid filter (SCHEMA_VIOLATION) or order ID (ORDER_IDENTIFIER_INVALID). IDs must be 26-character order IDs or positive invoice IDs. |
| 401 | The request is missing a valid API key. |
| 403 | Key cannot make this change. Codes: CUSTOMER_NOT_PERMITTED, NOT_PERMITTED, TERMS_NOT_PERMITTED (own brands only), OTHER_SUPPLIERS, MANUAL_PROMOTION (sales rep required), FEATURE_DISABLED, ORDER_ACTOR_REQUIRED (linked user required), USER_INACTIVE, ACTOR_SCOPE_MISMATCH (supplier key linked to a customer user). |
| 404 | No order with that identifier exists, the key cannot access it, or the order is an unsubmitted draft. Codes: ORDER_NOT_FOUND, REVISION_NOT_FOUND (the order has not been posted, so it has no pending changes). |
| 409 | The pending changes cannot be discarded right now. Codes: REVISION_PENDING (the changes are being processed), REVISION_SYNCING (the changes were applied and are waiting to sync), REVISION_DRAFT_IN_USE (another user last edited the pending change and no If-Match was sent). |
| 412 | If-Match no longer matches the order's version. Code: VERSION_MISMATCH. Fetch the order again and retry with its new version. |
| 429 | Rate limit exceeded. |
| 500 | Unexpected server error. |