Checkout Retail Dispense
Submits a retail dispense checkout supporting adult-use and medical customers, in-store and delivery dispense types, retailer and courier fulfillment, and multiple ticket items.
Requires the location JWT from the Tokens API in the
Authorization: Bearer <token> header.Request body fields
| Name | Type | Description |
|---|---|---|
internalRequestId required | UUID string | Identifies one logical dispense. Persist the UUID before submitting and reuse it when retrying after a timeout; a repeated checkout with an already processed identifier returns DUPLICATE_INTERNAL_REQUEST_ID. |
dispenseDateTime required |
ISO 8601 / RFC 3339 | UTC timestamp of the checkout request, for example 2026-08-07T16:20:14.000Z. |
customerType required | string | adult-use or medical. |
dispenseType required | string | in-store or delivery. |
medicalCannabisCardId | string | Required when customerType is medical. |
items[] required | array | One or more items with inventoryId, quantity, dollarAmount, and taxAmount. |
deliveryFulfillmentType | string | retailer or courier; required for delivery. |
deliveryAddress, travelRoute | string | Required for delivery dispenses. |
retailerDriverId, retailerVehicleId | integer | Required for retailer-fulfilled delivery. |
courierRetailAgreementId, courierDriverId, courierVehicleId | integer | Required for courier-fulfilled delivery. |
Request URL
—Request body preview
Server response
Successful Response
{
"status": "ok",
"message": "",
"data": {
"retailDispenseTicketId": 4217,
"barCode": "1234567890123456",
"internalRequestId": "7693ae51-135b-4755-a15e-f7aac21bc33a",
"totalItems": 2,
"totalDollarAmount": 40.00,
"totalTaxAmount": 3.20,
"grandTotalAmount": 43.20,
"totalThcMg": 520.00,
"delta9ThcMgTotal": 450.00,
"adultUseCannabisGramsTotal": 7.00,
"adultUseConcentrateGramsTotal": 0,
"adultUseCannabisMilligramsTotal": 0,
"medicalUnitsTotal": 0,
"plantCountTotal": 0,
"medicalIdentifierId": null
}
}
Response Fields
| Field | Type | Description |
|---|---|---|
retailDispenseTicketId | number | Internal parent ticket identifier. |
barCode | string | Generated order number/ticket barcode. |
internalRequestId | UUID string | Original logical dispense request identifier. |
totalItems | number | Number of distinct item rows. |
totalDollarAmount | number | Pre-tax order total. |
totalTaxAmount | number | Order tax total. |
grandTotalAmount | number | Sum of dollar and tax totals. |
totalThcMg | number | Total THC milligrams calculated for all items. |
delta9ThcMgTotal | number | Total Delta-9 THC milligrams calculated for all items. |
adultUseCannabisGramsTotal | number | Adult-Use cannabis-gram total. |
adultUseConcentrateGramsTotal | number | Adult-Use concentrate-gram total. |
adultUseCannabisMilligramsTotal | number | Adult-Use cannabis-milligram total. |
medicalUnitsTotal | number | Medical units used by the order; zero for an Adult-Use response. |
plantCountTotal | number | Plants represented by the order. |
medicalIdentifierId | string or null | Resolved patient identifier for a Medical dispense; null for Adult-Use. |
Endpoint-Specific Errors
| Error code | Typical message or condition | Meaning |
|---|---|---|
INVALID_DISPENSE | Dispense information is required or must be valid JSON. | The top-level dispense object was missing or malformed. |
DUPLICATE_INTERNAL_REQUEST_ID | This internal request ID has already been processed. | The logical sale identifier was already stored; reconcile through order lookup. |
DISPENSE_FAILED | Customer Type is required; Dispense Type is required; or another validation message. | A request-field or business-rule validation failed. |
ACTIVE_LOCATION_LICENSE_REQUIRED | Location lacks an eligible active license or temporary access. | The location cannot perform the requested dispense. |
INVENTORY_CASE_RESTRICTION | Message varies with the active case restriction. | One or more selected inventory items are blocked from dispensing. |
DISPENSE_FAILED | Medical units used exceed the patient's remaining medical units. | The proposed Medical order exceeds the authoritative current balance. |
DISPENSE_FAILED | Adult-Use or plant total cannot exceed the configured limit. | The proposed order exceeds a purchase limit. |
DISPENSE_FAILED | Inventory, testing, expiration, quantity, calculation, delivery, driver, vehicle, or courier validation message. | The authoritative checkout validation failed. |
Voids the specified retail dispense ticket and records the reason.
| Name | In | Description |
|---|---|---|
internalRequestIdrequired |
body UUID string |
Internal request identifier for the void operation. Generated automatically; reuse the same UUID when retrying the same void. |
retailDispenseTicketIdrequired |
path | Ticket identifier to void. |
voidReasonrequired |
body | Reason for voiding the ticket. |
Try it out
Request URL
—
Server response
Void: Eligibility and Safety Rules
The void is allowed only when all applicable conditions remain true:
- The ticket belongs to the authorized organization and location.
- The ticket was created through the POS API.
- The ticket currently has status
Complete. - The location has an active license or valid temporary access for the original Retail or Consumption room.
- The original ticket item rows and inventory records still exist.
- Each inventory item remains at the original organization and location.
- The inventory has no transfer, manifest, or destruction activity.
- Each ticket item retains its original dispense inventory-history link.
- The inventory has no later unsafe history event that prevents automatic reversal.
- Stored item quantities and package quantities are valid.
The route intentionally does not apply the administrative inventory-case restriction check. A case hold does not by itself prevent a safe void, although all downstream-activity and inventory-integrity checks still apply.
Inventory Restoration
- Packaged inventory has its package count increased by the dispensed package quantity.
- When an amount-per-package snapshot is available, packaged inventory also has its total cannabis material restored.
- Unpackaged weight or volume inventory has its total cannabis material increased by the dispensed quantity.
- Inventory deleted because the full available amount was sold is restored to active status.
- A new inventory-history record documents the restored quantity and the void reason.
- The parent ticket is updated to
Voidedand stores the void request ID, date/time, user, and reason.
Void Idempotency
- For this endpoint,
internalRequestIdidentifies one logical void and is stored asvoidInternalRequestId. The POS API should persist one UUID per intended void and reuse that same UUID when retrying after an uncertain response. - If the original void completed and the same ticket is retried with the same request ID, the endpoint returns a successful
Voidedresponse without restoring inventory a second time. - The idempotent retry response contains an empty
restoredItemsarray because the restoration occurred during the original call. - Reusing the void request ID for a different operation may return
DUPLICATE_VOID_INTERNAL_REQUEST_ID. - A new UUID must be generated for a different logical void.
Successful Response
{
"status": "ok",
"message": "",
"data": {
"retailDispenseTicketId": 4217,
"barCode": "1234567890123456",
"ticketStatus": "Voided",
"voidedDateTime": "2026-07-25T16:20:14.000Z",
"voidedByUserId": 1001,
"voidReason": "The POS user selected the wrong inventory item and confirmed that no downstream inventory activity occurred.",
"voidInternalRequestId": "f87a21f8-cf3a-46af-b4df-b25633499f14",
"restoredItems": [
{
"inventoryId": 1789,
"barCode": "9876543210987654",
"quantity": 2,
"unit": "pkg"
}
]
}
}
Idempotent Retry Response
{
"status": "ok",
"message": "",
"data": {
"retailDispenseTicketId": 4217,
"barCode": "1234567890123456",
"ticketStatus": "Voided",
"voidedDateTime": "2026-07-25T16:20:14.000Z",
"voidedByUserId": 1001,
"voidReason": "The POS user selected the wrong inventory item and confirmed that no downstream inventory activity occurred.",
"voidInternalRequestId": "f87a21f8-cf3a-46af-b4df-b25633499f14",
"restoredItems": []
}
}
Response Fields
| Field | Type | Description |
|---|---|---|
retailDispenseTicketId | number | Voided parent ticket identifier. |
barCode | string | Order number/ticket barcode. |
ticketStatus | string | Voided after a successful operation. |
voidedDateTime | date/time | UTC date/time stored for the void. |
voidedByUserId | number or string | Configured internal POS system user that performed the operation. |
voidReason | string | Stored reason for the void. |
voidInternalRequestId | UUID string | Logical void identifier used for idempotency. |
restoredItems | array | Inventory rows restored during this call. Empty for a successful idempotent retry. |
Restored Item Fields
| Field | Type | Description |
|---|---|---|
inventoryId | number | Inventory record restored. |
barCode | string | Compliance barcode of the restored inventory. |
quantity | number | Quantity restored. |
unit | string | Stored dispense unit: pkg, g, or mL. |
Endpoint-Specific Errors
| Error code | Meaning |
|---|---|
INVALID_DISPENSE_TICKET | The supplied ticket ID was missing or invalid. |
VOID_REASON_REQUIRED | A reason was not supplied. |
VOID_REASON_TOO_SHORT | The trimmed reason contains fewer than 50 characters. |
VOID_REASON_TOO_LONG | The reason exceeds 1,000 characters. |
DISPENSE_NOT_FOUND | The ticket was not found for the authorized organization and location. |
DISPENSE_ALREADY_VOIDED | The ticket was already voided with a different request ID. |
DISPENSE_NOT_COMPLETE | Only a completed ticket can be voided. |
DISPENSE_NOT_CREATED_THROUGH_POS | The ticket originated in the web application rather than the POS API. |
INVALID_DISPENSE_ROOM | The original Retail or Consumption room cannot be determined. |
ACTIVE_LOCATION_LICENSE_REQUIRED | The location lacks the license or temporary access required for the original room. |
DISPENSE_ITEMS_NOT_FOUND | The ticket's item rows are missing. |
INVENTORY_NOT_FOUND | One or more original inventory records no longer exist. |
INVENTORY_LEFT_LOCATION | An inventory item is no longer at the original organization and location. |
INVENTORY_HAS_DOWNSTREAM_ACTIVITY | A transfer, manifest, destruction, or later unsafe inventory event prevents automatic reversal. |
DISPENSE_HISTORY_LINK_MISSING | The original inventory-history reference needed for a safe reversal is missing. |
INVALID_DISPENSE_ITEM_QUANTITY | A stored quantity is invalid for restoration. |
DUPLICATE_VOID_INTERNAL_REQUEST_ID | The logical void request ID has already been used elsewhere. |
VOID_DISPENSE_FAILED | An unexpected void-processing error occurred. |