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.

OpenAPI JSON specification

Import an OpenAPI specification into Postman

There are two environments, each backed by its own isolated database and requiring a separate API key.

Production
api.kssdata.com
Live data. Use production API keys only. Data is updated continuously
Test
api.test.kssdata.com
Safe for development and integration testing. Data is updated every Sunday morning
⚠
API keys are environment-scoped. A production key will be rejected by the test environment and vice versa. Never use production keys in development or CI pipelines.

All endpoints are versioned under /api/v2. A full request to the products endpoint looks like:

https
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.

http header
x-api-key: your-api-key
Key Types

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.

request headers
POST /api/{{version}}/purchases
x-api-key: your-api-key
Idempotency-Key: 9f30f7f5-7e08-4f6a-a60c-8f7d11e9c702
↻
Exact retries replay the stored response. Replayed responses include 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:

RateLimit-Limit
The maximum number of requests allowed in the current window.
RateLimit-Remaining
Requests you have left before hitting the limit. 0 means the next request may be rejected.
RateLimit-Reset
Seconds until your remaining count recovers.
Retry-After
On a 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.

response headers
RateLimit-Limit: 1000
RateLimit-Remaining: 847
RateLimit-Reset: 2934
↻
Use pagination and filtering. Fetching only the data you need — by state, supplier, or customer — is the most effective way to reduce request volume.

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.

example request
GET /api/{{version}}/products?States=CA&Page=2&PageSize=100
json
{
  "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.

400 Bad Request
Missing or invalid parameter.
401 Unauthorized
Missing or invalid API key.
403 Forbidden
Your key does not have access to this resource.
404 Not Found
The endpoint does not exist.
429 Too Many Requests
Rate limit exceeded.
500 Server Error
Unexpected server error. Contact KSS support.
error response
{
  "Error": "InvoiceIDs parameter is required"
}

Response Headers

Alongside the rate limit headers, every response includes the following.

Request tracing

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.

Conditional requests

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.

conditional request
GET /api/{{version}}/products?States=CA
x-api-key: your-api-key
If-None-Match: W/"a1b2c3d4e5f6..."

Endpoints

Purchases

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.

Skip to endpoints →

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.

GET
/purchases
Not available to Customer keys Paginated Beta

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.

Parameters
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.

Responses
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.
Example Response
json
{
  "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
}
POST
/purchases
Not available to Customer keys Beta

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.

Callout:
po_num must be unique for the vendor, and that is what makes a retry safe: if a request times out after the purchase was already created, sending it again with the same po_num returns 409 DUPLICATE_PO_NUMBER instead of creating a second purchase order. The error's detail names the existing purchase's global ID, so you can go read it directly rather than searching for it.
Parameters
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.
Request Body
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.
application/json
{
  "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 Definitions
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.
Responses
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.
201 Response Headers
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.
Example Response
json
{
  "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
      }
    ]
  }
}
POST
/purchases/validate
Not available to Customer keys Beta

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.

Parameters
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.
Request Body
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.
application/json
{
  "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 Definitions
Field Description
valid
True when the purchase passed every validation check.
total_cases
Total case count derived across all line items.
Responses
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.
Example Response
json
{
  "data": {
    "valid": true,
    "total_cases": 30
  }
}
GET
/purchases/{purchaseID}
Not available to Customer keys Beta

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.

Parameters
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.
Responses
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.
Example Response
json
{
  "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"
      }
    ]
  }
}
PATCH
/purchases/{purchaseGlobalID}
Not available to Customer keys Beta

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.

Callout:
Including purchase_transactions replaces every line item on the purchase with the array you send, and an empty array removes them all. Leave the field out entirely to keep the existing line items. Status is never set here, use the submit action instead.
Parameters
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.
Request Body
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.
application/json
{
  "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"
    }
  ]
}
Responses
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.
200 Response Headers
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.
Example Response
json
{
  "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
      }
    ]
  }
}
DELETE
/purchases/{purchaseGlobalID}
Not available to Customer keys Beta

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.

Parameters
Name Type Required Description
purchaseGlobalID
path
string required
The global ID of the draft purchase to delete.
Responses
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.
POST
/purchases/{purchaseGlobalID}/submit
Not available to Customer keys Beta

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.

Parameters
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.
Responses
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.
200 Response Headers
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.
Example Response
json
{
  "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
      }
    ]
  }
}
API Key
GET
/me
Beta

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 Definitions
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.
Responses
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.
Example Response
json
{
  "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
    }
  }
}
Orders

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.

Skip to endpoints →

Draft
Build and review the order.
Submitted
Follow its processing progress.
Posted
View the invoice and delivery status.

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.

GET
/orders
Paginated Beta

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.

Callout:
Migrating from v1 GET /invoices? Use invoice_statuses=1 for its default view of New invoices. For conditional writes, use each order's version; the list ETag identifies the page.
Parameters
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 Definitions
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.
Responses
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.
Example Response
json
{
  "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
}
POST
/orders
Beta

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.

Callout:
Send Idempotency-Key to avoid duplicate orders on retries. Invalid case multiples or minimum quantities return 422; Prefer: handling=lenient adjusts them. Matching read-only fields are ignored; changed values return 409 FIELD_IMMUTABLE.
Parameters
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.
Request Body
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.
application/json
{
  "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 Definitions
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.
Responses
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.
201 Response Headers
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.
202 Response Headers
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.
Example Response
json
{
  "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"
        }
      }
    ]
  }
}
GET
/orders/creditTerms
Paginated Beta

Returns the payment terms that can be used as credit_term_id on an order line, ordered by term_id.

Parameters
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 Definitions
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.
Responses
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.
Example Response
json
{
  "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
}
POST
/orders/validate
Beta

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.

Callout:
Check valid, even on a 200 response: submission problems are reported in errors. A missed delivery cutoff is a warning for new orders and an error for posted edits. A blocked posted edit reports its reason under field order_id.
Parameters
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.
Request Body
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.
application/json
{
  "customer_id": 41001,
  "po_num": "PO-1042",
  "lines": [
    {
      "product_id": 30008,
      "cases": 10
    },
    {
      "product_id": 30009,
      "units": 48,
      "accept_backorder": true
    }
  ]
}
Field Definitions
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.
Responses
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.
Example Response
json
{
  "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
  }
}
GET
/orders/{order_id}
Beta

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.

Parameters
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 Definitions
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.
Responses
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.
200 Response Headers
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.
Example Response
json
{
  "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"
      }
    ]
  }
}
PATCH
/orders/{order_id}
Beta

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.

Callout:
Sending lines replaces ALL lines: include every line to keep. Matching product/promotion pairs retain their terms unless credit_term_id is sent. Check allowed_actions.update and send If-Match. Posted edits take effect after submission and processing; status cannot be set directly.
Parameters
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.
Request Body
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.
application/json
{
  "delivery_date": "2026-10-15",
  "lines": [
    {
      "product_id": 30008,
      "units": 144
    }
  ]
}
Field Definitions
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.
Responses
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.
200 Response Headers
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.
202 Response Headers
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.
Example Response
json
{
  "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"
      }
    ]
  }
}
DELETE
/orders/{order_id}
Beta

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.

Callout:
Submit or discard pending edits before cancelling. Poll GET /orders/{order_id}: cancellation.state progresses pending → applied → completed. If rejected, read cancellation.message and discard the request with DELETE /orders/{order_id}/revision before retrying.
Parameters
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 Definitions
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.
Responses
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.
202 Response Headers
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.
Example Response
json
{
  "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."
      }
    }
  }
}
POST
/orders/{order_id}/submit
Beta

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.

Callout:
No body is needed; only save_term_defaults is accepted. Send Idempotency-Key for safe retries. Missed delivery cutoffs move new orders to the next available date with a warning; posted edits return 422 DELIVERY_DATE_PAST_CUTOFF.
Parameters
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.
Request Body
Field Type Required Description
save_term_defaults
body
object optional
On submission, save selected custom payment terms as the customer's supplier defaults.
application/json
{
  "save_term_defaults": {
    "group_keys": [
      "shared:0:80003"
    ],
    "supplier_ids": [
      20001
    ]
  }
}
Field Definitions
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.
Responses
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.
202 Response Headers
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.
Example Response
json
{
  "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"
        }
      }
    ]
  }
}
DELETE
/orders/{order_id}/revision
Beta

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.

Parameters
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.
Responses
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.