Overview
Base URL
All API requests should be made to:
https://pos.itglonline.com/api/v1
Authentication
Use Laravel Sanctum Bearer tokens. Include in all requests:
Authorization: Bearer {token}
Content Type
All requests and responses use JSON format:
Content-Type: application/json
Accept: application/json
Rate Limiting
Default limits per endpoint type:
Standard: 60/min
POS lookups: 120/min
Bulk operations: 10/min
Generated Contract
This documentation and the downloadable JSON contract are generated from the Laravel route surface.
OpenAPI JSON
Generated Oct 31, 2026 07:30
Response Format
All API responses follow a consistent structure:
{
"success": true,
"data": { "...response data..." },
"meta": { "...pagination info (if applicable)..." }
}
{
"success": false,
"message": "Error description",
"errors": { "...validation errors (if applicable)..." }
}
Staff Mobile
Private selected-location staff orders, settlement, and finalized receipts.
Requires a named staff-mobile bearer token (legacy seven-day or renewable 15-minute access), live permissions: authenticated owner. Renewable access is checked against its revocable device family on every request. Accept: application/json. Selected location and every order/sale line must be accessible; inaccessible records return 404. Cache-Control: private, no-store. Money and nonserialized quantities are decimal strings; serialized quantities are integers. Display dates in Africa/Accra.
[]
{
"success": true,
"data": {
"devices": [
{
"id": "00000000-0000-4000-8000-000000000001",
"device_name": "Example iPhone",
"platform": "ios",
"last_renewed_at": "2026-10-04T12: 00: 00+00: 00",
"expires_at": "2026-11-13T12: 00: 00+00: 00",
"is_current": true
}
]
}
}
{
"success": false,
"code": "validation_failed",
"message": "The given data was invalid.",
"errors": {
"store_id": [
"Select an accessible active store."
]
}
}
Requires a named staff-mobile bearer token (legacy seven-day or renewable 15-minute access), live permissions: authenticated owner of a device family. Renewable access is checked against its revocable device family on every request. Accept: application/json. Selected location and every order/sale line must be accessible; inaccessible records return 404. Cache-Control: private, no-store. Money and nonserialized quantities are decimal strings; serialized quantities are integers. Display dates in Africa/Accra.
| Parameter | In | Type | Description |
|---|---|---|---|
| device_mode | body | string | personal or shared; shared turns biometrics off. |
| biometric_enabled | body | boolean | Only false is accepted: retires the refresh credential. Biometrics come back only through a new sign-in. |
{
"device_mode": "example",
"biometric_enabled": true
}
{
"success": true,
"data": {
"device_session": {
"id": "00000000-0000-4000-8000-000000000001",
"device_mode": "shared",
"biometric_enabled": false
}
}
}
{
"success": false,
"code": "validation_failed",
"message": "The given data was invalid.",
"errors": {
"store_id": [
"Select an accessible active store."
]
}
}
Requires a named staff-mobile bearer token (legacy seven-day or renewable 15-minute access), live permissions: authenticated owner. Renewable access is checked against its revocable device family on every request. Accept: application/json. Selected location and every order/sale line must be accessible; inaccessible records return 404. Cache-Control: private, no-store. Money and nonserialized quantities are decimal strings; serialized quantities are integers. Display dates in Africa/Accra. Requires a single-use X-Staff-Step-Up confirmation from auth/step-up when step-up enforcement and device security are on; a missing confirmation then returns 428 step_up_required, and an invalid one always does.
| Parameter | In | Type | Description |
|---|---|---|---|
| session *required | path | string | Owned device-session UUID; foreign ownership returns 404. |
[]
{
"success": true,
"data": {
"message": "Device signed out."
}
}
{
"success": false,
"code": "validation_failed",
"message": "The given data was invalid.",
"errors": {
"store_id": [
"Select an accessible active store."
]
}
}
Public password/TOTP sign-in, limited to five attempts per minute. Default unchanged legacy seven-day token. Explicit renewable_session=true requires device_id UUID, device_name and platform ios/android; feature-off returns 401 session_unavailable, never downgrade. Renewable response includes a 15-minute token and 30-day inactivity refresh credential. Missing TOTP returns 403 two_factor_required; invalid credentials/code return 422. Private, no-store.
| Parameter | In | Type | Description |
|---|---|---|---|
| email *required | body | string | Staff email. |
| password *required | body | string | Password. |
| device_name | body | string | Device token name, maximum 255 characters. |
| renewable_session | body | boolean | Explicit opt-in; issuance feature must be enabled. |
| pin_session | body | boolean | New sign-in flow: issue a PIN device family (device-security feature). Personal phones that also send biometric_session=true get Face ID refresh as well; feature-off returns the ordinary token with security.device_sessions=false. A PIN-only family gets a 60-minute first token. |
| biometric_session | body | boolean | With pin_session: also issue Face ID refresh on a personal phone. Ignored without pin_session, so a server with the feature off never issues a renewable family for it. |
| device_mode | body | string | personal (default) or shared. Shared store phones never use biometrics. |
| device_id | body | string | Required installation UUID for renewable enrollment. |
| platform | body | string | Required platform for renewable enrollment. |
| two_factor_code | body | string | Required six-digit TOTP when enabled. |
{
"email": "staff@example.invalid",
"password": "synthetic-password",
"device_name": "Example Device"
}
{
"success": true,
"data": {
"token": "SYNTHETIC-NONFUNCTIONAL-TOKEN",
"expires_at": "2026-10-11T12: 00: 00+00: 00",
"user": {
"id": 1,
"name": "Example Staff",
"email": "staff@example.invalid",
"role": {
"id": 1,
"name": "Staff",
"slug": "staff"
},
"store_id": 1,
"store_name": "Example Store",
"is_super_admin": false,
"has_two_factor": false
},
"permissions": [
"web-orders.view",
"fulfillment.manage",
"sales.create",
"sales.view"
]
}
}
{
"success": false,
"code": "validation_failed",
"message": "The given data was invalid.",
"errors": {
"email": [
"The provided credentials are incorrect."
]
}
}
Requires a named staff-mobile bearer token (legacy seven-day or renewable 15-minute access), live permissions: authenticated staff. Renewable access is checked against its revocable device family on every request. Accept: application/json. Selected location and every order/sale line must be accessible; inaccessible records return 404. Cache-Control: private, no-store. Money and nonserialized quantities are decimal strings; serialized quantities are integers. Display dates in Africa/Accra.
[]
{
"success": true,
"data": {
"message": "Successfully logged out."
}
}
{
"success": false,
"code": "validation_failed",
"message": "The given data was invalid.",
"errors": {
"store_id": [
"Select an accessible active store."
]
}
}
Requires a named staff-mobile bearer token (legacy seven-day or renewable 15-minute access), live permissions: authenticated owner. Renewable access is checked against its revocable device family on every request. Accept: application/json. Selected location and every order/sale line must be accessible; inaccessible records return 404. Cache-Control: private, no-store. Money and nonserialized quantities are decimal strings; serialized quantities are integers. Display dates in Africa/Accra. Requires a single-use X-Staff-Step-Up confirmation from auth/step-up when step-up enforcement and device security are on; a missing confirmation then returns 428 step_up_required, and an invalid one always does.
[]
{
"success": true,
"data": {
"message": "All trusted devices signed out."
}
}
{
"success": false,
"code": "validation_failed",
"message": "The given data was invalid.",
"errors": {
"store_id": [
"Select an accessible active store."
]
}
}
Public. Emails a 60-minute web reset link when the address belongs to an active staff account and answers the same either way. 3 requests/minute per address, 10 per IP. A reset signs the staff app out on every phone. Private, no-store.
| Parameter | In | Type | Description |
|---|---|---|---|
| email *required | body | string | Work email. |
{
"email": "staff@example.invalid"
}
{
"success": true,
"data": {
"message": "If that address has a staff account, a reset link is on its way. It works for 1 hour."
}
}
{
"success": false,
"code": "validation_failed",
"message": "The given data was invalid.",
"errors": {
"store_id": [
"Select an accessible active store."
]
}
}
Requires a named staff-mobile bearer token (legacy seven-day or renewable 15-minute access), live permissions: authenticated staff. Renewable access is checked against its revocable device family on every request. Accept: application/json. Selected location and every order/sale line must be accessible; inaccessible records return 404. Cache-Control: private, no-store. Money and nonserialized quantities are decimal strings; serialized quantities are integers. Display dates in Africa/Accra.
| Parameter | In | Type | Description |
|---|---|---|---|
| pin *required | body | string | New 6-digit PIN; repeats, runs and common codes are refused. |
| current_pin | body | string | Required to change an existing PIN unless the token comes from a password sign-in in the last 15 minutes. |
{
"pin": "example",
"current_pin": "example"
}
{
"success": true,
"data": {
"has_pin": true,
"pin_set_at": "2026-10-04T12: 00: 00+00: 00"
}
}
{
"success": false,
"code": "validation_failed",
"message": "The given data was invalid.",
"errors": {
"store_id": [
"Select an accessible active store."
]
}
}
Public secret-authenticated unlock, no access token required. 10 requests/minute per session and 30/minute per IP. Returns a 60-minute access token without a refresh credential. Wrong PINs return 422 pin_invalid with attempts_remaining; the fifth in a row locks the PIN (423 pin_locked) until a password sign-in. A device signed out by a person returns 401 device_signed_out with signed_out {reason, by, at}. Private, no-store.
| Parameter | In | Type | Description |
|---|---|---|---|
| session_id *required | body | string | PIN device-family UUID. |
| pin_secret *required | body | string | Opaque PIN-family secret from sign-in, kept in device secure storage. Maximum 512 characters. |
| pin *required | body | string | The person's staff PIN, 4 to 6 digits. Never log or persist. |
{
"session_id": "00000000-0000-4000-8000-000000000001",
"pin_secret": "SYNTHETIC-NONFUNCTIONAL-PIN-SECRET",
"pin": "000000"
}
{
"success": true,
"data": {
"token": "SYNTHETIC-NONFUNCTIONAL-TOKEN",
"expires_at": "2026-10-14T13: 00: 00+00: 00",
"refresh_expires_at": "2026-11-13T12: 00: 00+00: 00",
"device_session": {
"id": "00000000-0000-4000-8000-000000000001",
"device_id": "00000000-0000-4000-8000-000000000002",
"device_name": "Example iPhone",
"platform": "ios",
"device_mode": "personal",
"biometric_enabled": false
}
}
}
{
"success": false,
"code": "validation_failed",
"message": "The given data was invalid.",
"errors": {
"store_id": [
"Select an accessible active store."
]
}
}
Public secret-authenticated refresh, no access token required. 10 requests/minute per session and 30/minute per IP. Rotates atomically with 30-day rolling inactivity. Reuse with another request UUID retires the family. Private, no-store.
| Parameter | In | Type | Description |
|---|---|---|---|
| session_id *required | body | string | Device-session UUID. |
| refresh_token *required | body | string | Opaque protected credential, maximum 512 characters. |
| request_id *required | body | string | Persist UUID before dispatch. Same credential/UUID recovers the same result for less than 120 seconds. |
{
"session_id": "example",
"refresh_token": "example",
"request_id": "example"
}
{
"success": true,
"data": {
"token": "SYNTHETIC-NONFUNCTIONAL-TOKEN",
"expires_at": "2026-10-14T12: 15: 00+00: 00",
"refresh_token": "SYNTHETIC-NONFUNCTIONAL-REFRESH",
"refresh_expires_at": "2026-11-13T12: 00: 00+00: 00",
"device_session": {
"id": "00000000-0000-4000-8000-000000000001",
"device_id": "00000000-0000-4000-8000-000000000002",
"device_name": "Example iPhone",
"platform": "ios"
}
}
}
{
"success": false,
"code": "validation_failed",
"message": "The given data was invalid.",
"errors": {
"store_id": [
"Select an accessible active store."
]
}
}
The person's 20 most recent sign-ins and unlocks (password, biometric or pin), newest first. IP addresses are never returned. Private, no-store.
[]
{
"success": true,
"data": {
"sign_ins": [
{
"id": 1,
"method": "pin",
"device_name": "Example iPhone",
"platform": "ios",
"is_current_device": true,
"created_at": "2026-10-04T12: 00: 00+00: 00"
}
]
}
}
{
"success": false,
"code": "validation_failed",
"message": "The given data was invalid.",
"errors": {
"store_id": [
"Select an accessible active store."
]
}
}
Confirms one sensitive action on one record. Returns a single-use token valid for two minutes, sent as the X-Staff-Step-Up header on the guarded request; a retry of the same idempotent operation key may send it again. Biometric confirmations also return the rotated session bundle as data.session. Guarded routes return 428 step_up_required for a missing confirmation when enforcement (and device security) is on, and always for an invalid one. Signing out the current phone needs none. Private, no-store.
| Parameter | In | Type | Description |
|---|---|---|---|
| action *required | body | string | refund (credit note), return_refund (processing a return), void, cash_movement, cashier_close or device_signout. |
| subject_id *required | body | string | The record the action applies to: credit note, return, cashier session or device-family id; "all" for signing out all other phones. |
| method *required | body | string | pin or biometric. |
| pin | body | string | Required for pin. |
| session_id | body | string | Required for biometric: the current family, whose refresh credential was just released by Face ID or a fingerprint. |
| refresh_token | body | string | Required for biometric; rotated exactly like refresh. |
| request_id | body | string | Required for biometric; persist before dispatch. |
{
"action": "example",
"subject_id": "example",
"method": "example",
"pin": "example",
"session_id": "example",
"refresh_token": "example",
"request_id": "example"
}
{
"success": true,
"data": {
"step_up_token": "SYNTHETIC-NONFUNCTIONAL-STEP-UP",
"expires_at": "2026-10-04T12: 02: 00+00: 00"
}
}
{
"success": false,
"code": "validation_failed",
"message": "The given data was invalid.",
"errors": {
"store_id": [
"Select an accessible active store."
]
}
}
Requires a named staff-mobile bearer token (legacy seven-day or renewable 15-minute access), live permissions: authenticated staff. Renewable access is checked against its revocable device family on every request. Accept: application/json. Selected location and every order/sale line must be accessible; inaccessible records return 404. Cache-Control: private, no-store. Money and nonserialized quantities are decimal strings; serialized quantities are integers. Display dates in Africa/Accra.
[]
{
"success": true,
"data": {
"user": {
"id": 1,
"name": "Example Staff",
"email": "staff@example.invalid",
"role": {
"id": 1,
"name": "Staff",
"slug": "staff"
},
"store_id": 1,
"store_name": "Example Store",
"is_super_admin": false,
"has_two_factor": false
},
"permissions": [
"web-orders.view",
"fulfillment.manage",
"sales.create",
"sales.view"
],
"capabilities": {
"pos": false,
"cashier": false,
"web_orders": true,
"invoices": false,
"estimates": false,
"sales_history": false,
"customers": false,
"after_sales": false,
"reports": false,
"sale_void": false,
"server_version": "1.0.0"
},
"expires_at": "2026-10-11T12: 00: 00+00: 00",
"timezone": "Africa/Accra",
"branding": {
"merchant_name": "Example Merchant",
"logo_url": "https://example.invalid/logo.png",
"support_email": "support@example.invalid",
"support_phone": ""
},
"stores": [
{
"id": 1,
"name": "Example Store",
"warehouses": [
{
"id": 1,
"name": "Example Warehouse",
"is_sellable": true
}
]
}
]
}
}
{
"success": false,
"code": "validation_failed",
"message": "The given data was invalid.",
"errors": {
"store_id": [
"Select an accessible active store."
]
}
}
Requires a named staff-mobile bearer token (legacy seven-day or renewable 15-minute access), live permissions: cashier-sessions.view; POS feature enabled; owner or cashier-sessions.view-all supervisor. Renewable access is checked against its revocable device family on every request. Accept: application/json. Selected location and every order/sale line must be accessible; inaccessible records return 404. Cache-Control: private, no-store. Money and nonserialized quantities are decimal strings; serialized quantities are integers. Display dates in Africa/Accra.
| Parameter | In | Type | Description |
|---|---|---|---|
| store_id *required | query | integer | Selected accessible active store. |
| warehouse_id | query | integer | Optional selected accessible active warehouse. |
| page | query | integer | Page >= 1. |
| per_page | query | integer | Default 25; integer from 1 to 50. Out-of-range values return 422. |
{
"store_id": 1,
"warehouse_id": 1,
"page": 1,
"per_page": 1
}
{
"success": true,
"data": {
"items": [
{
"kind": "cashier_session",
"id": 1,
"revision": "opaque-example-revision",
"store_id": 1,
"warehouse_id": 1,
"user_id": 1,
"cash_register_id": null,
"status": "open",
"opened_at": "2026-10-04T12: 00: 00+00: 00",
"closed_at": null,
"opening_balance": {
"amount": "100.00",
"currency": "GHS",
"formatted": "GH\u20b5100.00"
},
"closing_balance": null,
"discrepancy": null,
"summary": {
"total_sales": {
"amount": "0.00",
"currency": "GHS",
"formatted": "GH\u20b50.00"
},
"total_cash_sales": {
"amount": "0.00",
"currency": "GHS",
"formatted": "GH\u20b50.00"
},
"total_card_sales": {
"amount": "0.00",
"currency": "GHS",
"formatted": "GH\u20b50.00"
},
"total_other_sales": {
"amount": "0.00",
"currency": "GHS",
"formatted": "GH\u20b50.00"
},
"total_refunds": {
"amount": "0.00",
"currency": "GHS",
"formatted": "GH\u20b50.00"
},
"transaction_count": 0,
"expected_balance": {
"amount": "100.00",
"currency": "GHS",
"formatted": "GH\u20b5100.00"
}
},
"allowed_actions": {
"close": true,
"suspend": true,
"resume": false,
"movements": true
}
}
]
},
"meta": {
"current_page": 1,
"last_page": 1,
"per_page": 25,
"total": 1
}
}
{
"success": false,
"code": "validation_failed",
"message": "The given data was invalid.",
"errors": {
"store_id": [
"Select an accessible active store."
]
}
}
Requires a named staff-mobile bearer token (legacy seven-day or renewable 15-minute access), live permissions: cashier-sessions.open; POS feature enabled; owner or cashier-sessions.view-all supervisor. Renewable access is checked against its revocable device family on every request. Accept: application/json. Selected location and every order/sale line must be accessible; inaccessible records return 404. Cache-Control: private, no-store. Money and nonserialized quantities are decimal strings; serialized quantities are integers. Display dates in Africa/Accra.
| Parameter | In | Type | Description |
|---|---|---|---|
| store_id *required | body | integer | Selected accessible active store. |
| warehouse_id | body | integer | Optional selected accessible active warehouse. |
| operation_key *required | body | string | Permanent actor-scoped UUID; committed replay precedes stale revision. |
| opening_balance *required | body | string | Nonnegative decimal string. |
| cash_register_id | body | integer | Active available register at exactly the selected location. |
| notes | body | string | Optional notes, maximum 500 characters. |
{
"store_id": 1,
"warehouse_id": 1,
"operation_key": "00000000-0000-4000-8000-000000000001",
"opening_balance": "100.00"
}
{
"success": true,
"data": {
"operation": {
"key": "00000000-0000-4000-8000-000000000001",
"replayed": false
},
"result_ids": {
"cashier_session_id": 1
},
"resource": {
"kind": "cashier_session",
"id": 1,
"revision": "opaque-example-revision",
"store_id": 1,
"warehouse_id": 1,
"user_id": 1,
"cash_register_id": null,
"status": "open",
"opened_at": "2026-10-04T12: 00: 00+00: 00",
"closed_at": null,
"opening_balance": {
"amount": "100.00",
"currency": "GHS",
"formatted": "GH\u20b5100.00"
},
"closing_balance": null,
"discrepancy": null,
"summary": {
"total_sales": {
"amount": "0.00",
"currency": "GHS",
"formatted": "GH\u20b50.00"
},
"total_cash_sales": {
"amount": "0.00",
"currency": "GHS",
"formatted": "GH\u20b50.00"
},
"total_card_sales": {
"amount": "0.00",
"currency": "GHS",
"formatted": "GH\u20b50.00"
},
"total_other_sales": {
"amount": "0.00",
"currency": "GHS",
"formatted": "GH\u20b50.00"
},
"total_refunds": {
"amount": "0.00",
"currency": "GHS",
"formatted": "GH\u20b50.00"
},
"transaction_count": 0,
"expected_balance": {
"amount": "100.00",
"currency": "GHS",
"formatted": "GH\u20b5100.00"
}
},
"allowed_actions": {
"close": true,
"suspend": true,
"resume": false,
"movements": true
}
}
}
}
{
"success": false,
"code": "validation_failed",
"message": "The given data was invalid.",
"errors": {
"store_id": [
"Select an accessible active store."
]
}
}
Requires a named staff-mobile bearer token (legacy seven-day or renewable 15-minute access), live permissions: cashier-sessions.view; POS feature enabled; owner or cashier-sessions.view-all supervisor. Renewable access is checked against its revocable device family on every request. Accept: application/json. Selected location and every order/sale line must be accessible; inaccessible records return 404. Cache-Control: private, no-store. Money and nonserialized quantities are decimal strings; serialized quantities are integers. Display dates in Africa/Accra.
| Parameter | In | Type | Description |
|---|---|---|---|
| store_id *required | query | integer | Selected accessible active store. |
| warehouse_id | query | integer | Optional selected accessible active warehouse. |
{
"store_id": 1,
"warehouse_id": 1
}
{
"success": true,
"data": {
"session": {
"kind": "cashier_session",
"id": 1,
"revision": "opaque-example-revision",
"store_id": 1,
"warehouse_id": 1,
"user_id": 1,
"cash_register_id": null,
"status": "open",
"opened_at": "2026-10-04T12: 00: 00+00: 00",
"closed_at": null,
"opening_balance": {
"amount": "100.00",
"currency": "GHS",
"formatted": "GH\u20b5100.00"
},
"closing_balance": null,
"discrepancy": null,
"summary": {
"total_sales": {
"amount": "0.00",
"currency": "GHS",
"formatted": "GH\u20b50.00"
},
"total_cash_sales": {
"amount": "0.00",
"currency": "GHS",
"formatted": "GH\u20b50.00"
},
"total_card_sales": {
"amount": "0.00",
"currency": "GHS",
"formatted": "GH\u20b50.00"
},
"total_other_sales": {
"amount": "0.00",
"currency": "GHS",
"formatted": "GH\u20b50.00"
},
"total_refunds": {
"amount": "0.00",
"currency": "GHS",
"formatted": "GH\u20b50.00"
},
"transaction_count": 0,
"expected_balance": {
"amount": "100.00",
"currency": "GHS",
"formatted": "GH\u20b5100.00"
}
},
"allowed_actions": {
"close": true,
"suspend": true,
"resume": false,
"movements": true
}
}
}
}
{
"success": false,
"code": "validation_failed",
"message": "The given data was invalid.",
"errors": {
"store_id": [
"Select an accessible active store."
]
}
}
Requires a named staff-mobile bearer token (legacy seven-day or renewable 15-minute access), live permissions: cashier-sessions.view; POS feature enabled; owner or cashier-sessions.view-all supervisor. Renewable access is checked against its revocable device family on every request. Accept: application/json. Selected location and every order/sale line must be accessible; inaccessible records return 404. Cache-Control: private, no-store. Money and nonserialized quantities are decimal strings; serialized quantities are integers. Display dates in Africa/Accra.
| Parameter | In | Type | Description |
|---|---|---|---|
| session *required | path | string | Path parameter: Session |
| store_id *required | query | integer | Selected accessible active store. |
| warehouse_id | query | integer | Optional selected accessible active warehouse. |
{
"store_id": 1,
"warehouse_id": 1
}
{
"success": true,
"data": {
"session": {
"kind": "cashier_session",
"id": 1,
"revision": "opaque-example-revision",
"store_id": 1,
"warehouse_id": 1,
"user_id": 1,
"cash_register_id": null,
"status": "open",
"opened_at": "2026-10-04T12: 00: 00+00: 00",
"closed_at": null,
"opening_balance": {
"amount": "100.00",
"currency": "GHS",
"formatted": "GH\u20b5100.00"
},
"closing_balance": null,
"discrepancy": null,
"summary": {
"total_sales": {
"amount": "0.00",
"currency": "GHS",
"formatted": "GH\u20b50.00"
},
"total_cash_sales": {
"amount": "0.00",
"currency": "GHS",
"formatted": "GH\u20b50.00"
},
"total_card_sales": {
"amount": "0.00",
"currency": "GHS",
"formatted": "GH\u20b50.00"
},
"total_other_sales": {
"amount": "0.00",
"currency": "GHS",
"formatted": "GH\u20b50.00"
},
"total_refunds": {
"amount": "0.00",
"currency": "GHS",
"formatted": "GH\u20b50.00"
},
"transaction_count": 0,
"expected_balance": {
"amount": "100.00",
"currency": "GHS",
"formatted": "GH\u20b5100.00"
}
},
"allowed_actions": {
"close": true,
"suspend": true,
"resume": false,
"movements": true
}
}
}
}
{
"success": false,
"code": "validation_failed",
"message": "The given data was invalid.",
"errors": {
"store_id": [
"Select an accessible active store."
]
}
}
Requires a named staff-mobile bearer token (legacy seven-day or renewable 15-minute access), live permissions: cashier-sessions.close; POS feature enabled; owner or cashier-sessions.view-all supervisor. Renewable access is checked against its revocable device family on every request. Accept: application/json. Selected location and every order/sale line must be accessible; inaccessible records return 404. Cache-Control: private, no-store. Money and nonserialized quantities are decimal strings; serialized quantities are integers. Display dates in Africa/Accra. Requires a single-use X-Staff-Step-Up confirmation from auth/step-up when step-up enforcement and device security are on; a missing confirmation then returns 428 step_up_required, and an invalid one always does.
| Parameter | In | Type | Description |
|---|---|---|---|
| session *required | path | string | Path parameter: Session |
| store_id *required | body | integer | Selected accessible active store. |
| warehouse_id | body | integer | Optional selected accessible active warehouse. |
| operation_key *required | body | string | Permanent actor-scoped UUID; committed replay precedes stale revision. |
| revision *required | body | string | Fresh opaque session revision. Required except when opening. |
| closing_balance *required | body | string | Counted cash, nonnegative decimal string. Server calculates expected balance and variance. |
| discrepancy_notes | body | string | Required for nonzero variance; maximum 500 characters. |
| notes | body | string | Optional notes, maximum 1000 characters. |
{
"store_id": 1,
"warehouse_id": 1,
"operation_key": "00000000-0000-4000-8000-000000000001",
"revision": "opaque-example-revision",
"closing_balance": "100.00"
}
{
"success": true,
"data": {
"operation": {
"key": "00000000-0000-4000-8000-000000000001",
"replayed": false
},
"result_ids": {
"cashier_session_id": 1
},
"resource": {
"kind": "cashier_session",
"id": 1,
"revision": "opaque-example-revision",
"store_id": 1,
"warehouse_id": 1,
"user_id": 1,
"cash_register_id": null,
"status": "closed",
"opened_at": "2026-10-04T12: 00: 00+00: 00",
"closed_at": "2026-10-04T12: 00: 00+00: 00",
"opening_balance": {
"amount": "100.00",
"currency": "GHS",
"formatted": "GH\u20b5100.00"
},
"closing_balance": {
"amount": "100.00",
"currency": "GHS",
"formatted": "GH\u20b5100.00"
},
"discrepancy": {
"amount": "0.00",
"currency": "GHS",
"formatted": "GH\u20b50.00"
},
"summary": {
"total_sales": {
"amount": "0.00",
"currency": "GHS",
"formatted": "GH\u20b50.00"
},
"total_cash_sales": {
"amount": "0.00",
"currency": "GHS",
"formatted": "GH\u20b50.00"
},
"total_card_sales": {
"amount": "0.00",
"currency": "GHS",
"formatted": "GH\u20b50.00"
},
"total_other_sales": {
"amount": "0.00",
"currency": "GHS",
"formatted": "GH\u20b50.00"
},
"total_refunds": {
"amount": "0.00",
"currency": "GHS",
"formatted": "GH\u20b50.00"
},
"transaction_count": 0,
"expected_balance": {
"amount": "100.00",
"currency": "GHS",
"formatted": "GH\u20b5100.00"
}
},
"allowed_actions": {
"close": true,
"suspend": true,
"resume": false,
"movements": true
}
}
}
}
{
"success": false,
"code": "validation_failed",
"message": "The given data was invalid.",
"errors": {
"store_id": [
"Select an accessible active store."
]
}
}
Requires a named staff-mobile bearer token (legacy seven-day or renewable 15-minute access), live permissions: cashier-sessions.movements; POS feature enabled; owner or cashier-sessions.view-all supervisor. Renewable access is checked against its revocable device family on every request. Accept: application/json. Selected location and every order/sale line must be accessible; inaccessible records return 404. Cache-Control: private, no-store. Money and nonserialized quantities are decimal strings; serialized quantities are integers. Display dates in Africa/Accra. Requires a single-use X-Staff-Step-Up confirmation from auth/step-up when step-up enforcement and device security are on; a missing confirmation then returns 428 step_up_required, and an invalid one always does.
| Parameter | In | Type | Description |
|---|---|---|---|
| session *required | path | string | Path parameter: Session |
| store_id *required | body | integer | Selected accessible active store. |
| warehouse_id | body | integer | Optional selected accessible active warehouse. |
| operation_key *required | body | string | Permanent actor-scoped UUID; committed replay precedes stale revision. |
| revision *required | body | string | Fresh opaque session revision. Required except when opening. |
| type *required | body | string | cash_in, cash_out, float, pickup, drop, adjustment; existing accounting semantics. |
| amount *required | body | string | Positive decimal string, minimum 0.01. |
| reason *required | body | string | Reason, maximum 255 characters. |
| reference | body | string | Optional reference, maximum 100 characters. |
| notes | body | string | Optional notes, maximum 500 characters. |
{
"store_id": 1,
"warehouse_id": 1,
"operation_key": "00000000-0000-4000-8000-000000000001",
"revision": "opaque-example-revision",
"type": "cash_in",
"amount": "10.00",
"reason": "other"
}
{
"success": true,
"data": {
"operation": {
"key": "00000000-0000-4000-8000-000000000001",
"replayed": false
},
"result_ids": {
"cashier_session_id": 1
},
"resource": {
"kind": "cashier_session",
"id": 1,
"revision": "opaque-example-revision",
"store_id": 1,
"warehouse_id": 1,
"user_id": 1,
"cash_register_id": null,
"status": "open",
"opened_at": "2026-10-04T12: 00: 00+00: 00",
"closed_at": null,
"opening_balance": {
"amount": "100.00",
"currency": "GHS",
"formatted": "GH\u20b5100.00"
},
"closing_balance": null,
"discrepancy": null,
"summary": {
"total_sales": {
"amount": "0.00",
"currency": "GHS",
"formatted": "GH\u20b50.00"
},
"total_cash_sales": {
"amount": "0.00",
"currency": "GHS",
"formatted": "GH\u20b50.00"
},
"total_card_sales": {
"amount": "0.00",
"currency": "GHS",
"formatted": "GH\u20b50.00"
},
"total_other_sales": {
"amount": "0.00",
"currency": "GHS",
"formatted": "GH\u20b50.00"
},
"total_refunds": {
"amount": "0.00",
"currency": "GHS",
"formatted": "GH\u20b50.00"
},
"transaction_count": 0,
"expected_balance": {
"amount": "100.00",
"currency": "GHS",
"formatted": "GH\u20b5100.00"
}
},
"allowed_actions": {
"close": true,
"suspend": true,
"resume": false,
"movements": true
}
}
}
}
{
"success": false,
"code": "validation_failed",
"message": "The given data was invalid.",
"errors": {
"store_id": [
"Select an accessible active store."
]
}
}
Requires a named staff-mobile bearer token (legacy seven-day or renewable 15-minute access), live permissions: cashier-sessions.close; POS feature enabled; owner or cashier-sessions.view-all supervisor. Renewable access is checked against its revocable device family on every request. Accept: application/json. Selected location and every order/sale line must be accessible; inaccessible records return 404. Cache-Control: private, no-store. Money and nonserialized quantities are decimal strings; serialized quantities are integers. Display dates in Africa/Accra.
| Parameter | In | Type | Description |
|---|---|---|---|
| session *required | path | string | Path parameter: Session |
| store_id *required | body | integer | Selected accessible active store. |
| warehouse_id | body | integer | Optional selected accessible active warehouse. |
| operation_key *required | body | string | Permanent actor-scoped UUID; committed replay precedes stale revision. |
| revision *required | body | string | Fresh opaque session revision. Required except when opening. |
| notes | body | string | Optional notes, maximum 500 characters. |
{
"store_id": 1,
"warehouse_id": 1,
"operation_key": "00000000-0000-4000-8000-000000000001",
"revision": "opaque-example-revision"
}
{
"success": true,
"data": {
"operation": {
"key": "00000000-0000-4000-8000-000000000001",
"replayed": false
},
"result_ids": {
"cashier_session_id": 1
},
"resource": {
"kind": "cashier_session",
"id": 1,
"revision": "opaque-example-revision",
"store_id": 1,
"warehouse_id": 1,
"user_id": 1,
"cash_register_id": null,
"status": "open",
"opened_at": "2026-10-04T12: 00: 00+00: 00",
"closed_at": null,
"opening_balance": {
"amount": "100.00",
"currency": "GHS",
"formatted": "GH\u20b5100.00"
},
"closing_balance": null,
"discrepancy": null,
"summary": {
"total_sales": {
"amount": "0.00",
"currency": "GHS",
"formatted": "GH\u20b50.00"
},
"total_cash_sales": {
"amount": "0.00",
"currency": "GHS",
"formatted": "GH\u20b50.00"
},
"total_card_sales": {
"amount": "0.00",
"currency": "GHS",
"formatted": "GH\u20b50.00"
},
"total_other_sales": {
"amount": "0.00",
"currency": "GHS",
"formatted": "GH\u20b50.00"
},
"total_refunds": {
"amount": "0.00",
"currency": "GHS",
"formatted": "GH\u20b50.00"
},
"transaction_count": 0,
"expected_balance": {
"amount": "100.00",
"currency": "GHS",
"formatted": "GH\u20b5100.00"
}
},
"allowed_actions": {
"close": true,
"suspend": true,
"resume": false,
"movements": true
}
}
}
}
{
"success": false,
"code": "validation_failed",
"message": "The given data was invalid.",
"errors": {
"store_id": [
"Select an accessible active store."
]
}
}
Requires a named staff-mobile bearer token (legacy seven-day or renewable 15-minute access), live permissions: cashier-sessions.view; POS feature enabled; owner or cashier-sessions.view-all supervisor. Renewable access is checked against its revocable device family on every request. Accept: application/json. Selected location and every order/sale line must be accessible; inaccessible records return 404. Cache-Control: private, no-store. Money and nonserialized quantities are decimal strings; serialized quantities are integers. Display dates in Africa/Accra.
| Parameter | In | Type | Description |
|---|---|---|---|
| session *required | path | string | Path parameter: Session |
| store_id *required | query | integer | Selected accessible active store. |
| warehouse_id | query | integer | Optional selected accessible active warehouse. |
{
"store_id": 1,
"warehouse_id": 1
}
{
"success": true,
"data": {
"summary": {
"total_sales": {
"amount": "0.00",
"currency": "GHS",
"formatted": "GH\u20b50.00"
},
"total_cash_sales": {
"amount": "0.00",
"currency": "GHS",
"formatted": "GH\u20b50.00"
},
"total_card_sales": {
"amount": "0.00",
"currency": "GHS",
"formatted": "GH\u20b50.00"
},
"total_other_sales": {
"amount": "0.00",
"currency": "GHS",
"formatted": "GH\u20b50.00"
},
"total_refunds": {
"amount": "0.00",
"currency": "GHS",
"formatted": "GH\u20b50.00"
},
"transaction_count": 0,
"expected_balance": {
"amount": "100.00",
"currency": "GHS",
"formatted": "GH\u20b5100.00"
}
},
"revision": "opaque-example-revision"
}
}
{
"success": false,
"code": "validation_failed",
"message": "The given data was invalid.",
"errors": {
"store_id": [
"Select an accessible active store."
]
}
}
Requires a named staff-mobile bearer token (legacy seven-day or renewable 15-minute access), live permissions: cashier-sessions.close; POS feature enabled; owner or cashier-sessions.view-all supervisor. Renewable access is checked against its revocable device family on every request. Accept: application/json. Selected location and every order/sale line must be accessible; inaccessible records return 404. Cache-Control: private, no-store. Money and nonserialized quantities are decimal strings; serialized quantities are integers. Display dates in Africa/Accra.
| Parameter | In | Type | Description |
|---|---|---|---|
| session *required | path | string | Path parameter: Session |
| store_id *required | body | integer | Selected accessible active store. |
| warehouse_id | body | integer | Optional selected accessible active warehouse. |
| operation_key *required | body | string | Permanent actor-scoped UUID; committed replay precedes stale revision. |
| revision *required | body | string | Fresh opaque session revision. Required except when opening. |
| reason | body | string | Optional reason, maximum 255 characters. |
| notes | body | string | Optional notes, maximum 500 characters. |
{
"store_id": 1,
"warehouse_id": 1,
"operation_key": "00000000-0000-4000-8000-000000000001",
"revision": "opaque-example-revision"
}
{
"success": true,
"data": {
"operation": {
"key": "00000000-0000-4000-8000-000000000001",
"replayed": false
},
"result_ids": {
"cashier_session_id": 1
},
"resource": {
"kind": "cashier_session",
"id": 1,
"revision": "opaque-example-revision",
"store_id": 1,
"warehouse_id": 1,
"user_id": 1,
"cash_register_id": null,
"status": "suspended",
"opened_at": "2026-10-04T12: 00: 00+00: 00",
"closed_at": null,
"opening_balance": {
"amount": "100.00",
"currency": "GHS",
"formatted": "GH\u20b5100.00"
},
"closing_balance": null,
"discrepancy": null,
"summary": {
"total_sales": {
"amount": "0.00",
"currency": "GHS",
"formatted": "GH\u20b50.00"
},
"total_cash_sales": {
"amount": "0.00",
"currency": "GHS",
"formatted": "GH\u20b50.00"
},
"total_card_sales": {
"amount": "0.00",
"currency": "GHS",
"formatted": "GH\u20b50.00"
},
"total_other_sales": {
"amount": "0.00",
"currency": "GHS",
"formatted": "GH\u20b50.00"
},
"total_refunds": {
"amount": "0.00",
"currency": "GHS",
"formatted": "GH\u20b50.00"
},
"transaction_count": 0,
"expected_balance": {
"amount": "100.00",
"currency": "GHS",
"formatted": "GH\u20b5100.00"
}
},
"allowed_actions": {
"close": true,
"suspend": true,
"resume": false,
"movements": true
}
}
}
}
{
"success": false,
"code": "validation_failed",
"message": "The given data was invalid.",
"errors": {
"store_id": [
"Select an accessible active store."
]
}
}
Requires current after-sales capability and credit_notes.view plus action authority. Whole source invoice, note and target scopes are checked before serialization/pagination. Permanent UUID replay reauthorizes current result ownership before mutable revision/state checks. No provider calls, client approver/payable/tax/totals or original serial override. Cache-Control: private, no-store.
| Parameter | In | Type | Description |
|---|---|---|---|
| store_id *required | query | integer | Selected accessible active store. |
| warehouse_id | query | integer | Optional selected accessible active warehouse. |
| q | query | string | Literal number search, max100. |
| page | query | integer | Page, minimum1. |
| per_page | query | integer | Default25, maximum50. |
| invoice_id | query | integer | Exact source invoice filter. |
| status | query | string | draft,pending_approval,approved,applied,voided. |
{
"store_id": 1,
"warehouse_id": 1,
"q": "example",
"page": 1,
"per_page": 1,
"invoice_id": 1,
"status": "example"
}
{
"success": true,
"data": {
"items": [
{
"kind": "credit_note",
"id": 1,
"invoice_id": 1,
"number": "CN-202610-0001",
"store_id": 1,
"warehouse_id": 1,
"type": "return",
"status": "draft",
"revision": "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa",
"customer": {
"id": 1,
"name": "Stored Customer"
},
"subtotal": {
"amount": "12.50",
"currency": "GHS",
"formatted": "GH\u20b512.50"
},
"tax": {
"amount": "0.00",
"currency": "GHS",
"formatted": "GH\u20b50.00"
},
"total": {
"amount": "12.50",
"currency": "GHS",
"formatted": "GH\u20b512.50"
},
"consumed": {
"amount": "0.00",
"currency": "GHS",
"formatted": "GH\u20b50.00"
},
"applied": {
"amount": "0.00",
"currency": "GHS",
"formatted": "GH\u20b50.00"
},
"refunded": {
"amount": "0.00",
"currency": "GHS",
"formatted": "GH\u20b50.00"
},
"remaining": {
"amount": "12.50",
"currency": "GHS",
"formatted": "GH\u20b512.50"
},
"reason": "Example credit",
"voided_reason": null,
"requires_approval": true,
"created_at": "2026-10-04T12: 00: 00+00: 00",
"approved_at": null,
"voided_at": null,
"items": [
{
"id": 1,
"invoice_item_id": 1,
"name": "Stored Product",
"sku": "STORED-SKU",
"warehouse_id": 1,
"quantity": "1.000",
"base_quantity": "1.000000",
"unit": {
"id": 1,
"name": "pc"
},
"serial_number": null,
"condition": "resellable",
"restock": true,
"restocked": false,
"amount": {
"amount": "12.50",
"currency": "GHS",
"formatted": "GH\u20b512.50"
},
"tax": {
"amount": "0.00",
"currency": "GHS",
"formatted": "GH\u20b50.00"
}
}
],
"applications": [],
"refund_plan": {
"available": false,
"requires_gateway": false,
"maximum": {
"amount": "0.00",
"currency": "GHS",
"formatted": "GH\u20b50.00"
},
"methods": [
{
"id": 1,
"name": "Cash",
"code": "cash"
}
]
},
"allowed_actions": {
"submit": true,
"approve": false,
"reject": false,
"void": true,
"apply": false,
"refund": false
},
"source_workflow": "invoice",
"unavailable_reason": null,
"source_sale": null
}
]
},
"meta": {
"current_page": 1,
"last_page": 1,
"per_page": 25,
"total": 1
}
}
{
"success": false,
"code": "validation_failed",
"message": "The given data was invalid.",
"errors": {
"store_id": [
"Select an accessible active store."
]
}
}
Requires current after-sales capability and credit_notes.view plus action authority. Whole source invoice, note and target scopes are checked before serialization/pagination. Permanent UUID replay reauthorizes current result ownership before mutable revision/state checks. No provider calls, client approver/payable/tax/totals or original serial override. Cache-Control: private, no-store. Refund maximum is min(remaining credit, attributable persisted manual invoice receipts minus all committed prior refunds), including deleted/voided source-note evidence. Unknown or contradictory provenance fails closed. No WHT, credit or linked-Sale amounts count as receipt.
| Parameter | In | Type | Description |
|---|---|---|---|
| creditNote *required | path | string | Path parameter: Credit Note |
| store_id *required | query | integer | Selected accessible active store. |
| warehouse_id | query | integer | Optional selected accessible active warehouse. |
{
"store_id": 1,
"warehouse_id": 1
}
{
"success": true,
"data": {
"kind": "credit_note",
"id": 1,
"invoice_id": 1,
"number": "CN-202610-0001",
"store_id": 1,
"warehouse_id": 1,
"type": "return",
"status": "draft",
"revision": "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa",
"customer": {
"id": 1,
"name": "Stored Customer"
},
"subtotal": {
"amount": "12.50",
"currency": "GHS",
"formatted": "GH\u20b512.50"
},
"tax": {
"amount": "0.00",
"currency": "GHS",
"formatted": "GH\u20b50.00"
},
"total": {
"amount": "12.50",
"currency": "GHS",
"formatted": "GH\u20b512.50"
},
"consumed": {
"amount": "0.00",
"currency": "GHS",
"formatted": "GH\u20b50.00"
},
"applied": {
"amount": "0.00",
"currency": "GHS",
"formatted": "GH\u20b50.00"
},
"refunded": {
"amount": "0.00",
"currency": "GHS",
"formatted": "GH\u20b50.00"
},
"remaining": {
"amount": "12.50",
"currency": "GHS",
"formatted": "GH\u20b512.50"
},
"reason": "Example credit",
"voided_reason": null,
"requires_approval": true,
"created_at": "2026-10-04T12: 00: 00+00: 00",
"approved_at": null,
"voided_at": null,
"items": [
{
"id": 1,
"invoice_item_id": 1,
"name": "Stored Product",
"sku": "STORED-SKU",
"warehouse_id": 1,
"quantity": "1.000",
"base_quantity": "1.000000",
"unit": {
"id": 1,
"name": "pc"
},
"serial_number": null,
"condition": "resellable",
"restock": true,
"restocked": false,
"amount": {
"amount": "12.50",
"currency": "GHS",
"formatted": "GH\u20b512.50"
},
"tax": {
"amount": "0.00",
"currency": "GHS",
"formatted": "GH\u20b50.00"
}
}
],
"applications": [],
"refund_plan": {
"available": false,
"requires_gateway": false,
"maximum": {
"amount": "0.00",
"currency": "GHS",
"formatted": "GH\u20b50.00"
},
"methods": [
{
"id": 1,
"name": "Cash",
"code": "cash"
}
]
},
"allowed_actions": {
"submit": true,
"approve": false,
"reject": false,
"void": true,
"apply": false,
"refund": false
},
"source_workflow": "invoice",
"unavailable_reason": null,
"source_sale": null
}
}
{
"success": false,
"code": "validation_failed",
"message": "The given data was invalid.",
"errors": {
"store_id": [
"Select an accessible active store."
]
}
}
Requires current after-sales capability and credit_notes.view plus action authority. Whole source invoice, note and target scopes are checked before serialization/pagination. Permanent UUID replay reauthorizes current result ownership before mutable revision/state checks. No provider calls, client approver/payable/tax/totals or original serial override. Cache-Control: private, no-store. Manual refund acknowledges a ledger operation, never a gateway transfer. Shared web/native writers lock actor, sorted source/target invoice roots, note and stock/payment inputs before reauthorizing. Unknown fields rejected.
| Parameter | In | Type | Description |
|---|---|---|---|
| creditNote *required | path | string | Path parameter: Credit Note |
| store_id *required | body | integer | Selected accessible active store. |
| warehouse_id | body | integer | Optional selected accessible active warehouse. |
| operation_key *required | body | string | Permanent UUID. |
| revision *required | body | string | Current 64-character source Invoice revision for creation; CreditNote revision for all actions. |
| amount *required | body | string | Positive decimal Money intent, max10 integer and2 fractional digits. |
| notes | body | string | Maximum2000 characters; never persisted in operation marker. |
| invoice_id *required | body | integer | Scoped same-customer eligible target. |
{
"store_id": 1,
"warehouse_id": 1,
"operation_key": "00000000-0000-4000-8000-000000000001",
"revision": "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa",
"amount": "12.50",
"invoice_id": 1
}
{
"success": true,
"data": {
"operation": {
"key": "00000000-0000-4000-8000-000000000001",
"replayed": false
},
"result_ids": {
"credit_note_id": 1,
"credit_note_application_id": 1
},
"resource": {
"kind": "credit_note",
"id": 1,
"invoice_id": 1,
"number": "CN-202610-0001",
"store_id": 1,
"warehouse_id": 1,
"type": "return",
"status": "applied",
"revision": "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa",
"customer": {
"id": 1,
"name": "Stored Customer"
},
"subtotal": {
"amount": "12.50",
"currency": "GHS",
"formatted": "GH\u20b512.50"
},
"tax": {
"amount": "0.00",
"currency": "GHS",
"formatted": "GH\u20b50.00"
},
"total": {
"amount": "12.50",
"currency": "GHS",
"formatted": "GH\u20b512.50"
},
"consumed": {
"amount": "12.50",
"currency": "GHS",
"formatted": "GH\u20b512.50"
},
"applied": {
"amount": "12.50",
"currency": "GHS",
"formatted": "GH\u20b512.50"
},
"refunded": {
"amount": "0.00",
"currency": "GHS",
"formatted": "GH\u20b50.00"
},
"remaining": {
"amount": "0.00",
"currency": "GHS",
"formatted": "GH\u20b50.00"
},
"reason": "Example credit",
"voided_reason": null,
"requires_approval": true,
"created_at": "2026-10-04T12: 00: 00+00: 00",
"approved_at": null,
"voided_at": null,
"items": [
{
"id": 1,
"invoice_item_id": 1,
"name": "Stored Product",
"sku": "STORED-SKU",
"warehouse_id": 1,
"quantity": "1.000",
"base_quantity": "1.000000",
"unit": {
"id": 1,
"name": "pc"
},
"serial_number": null,
"condition": "resellable",
"restock": true,
"restocked": false,
"amount": {
"amount": "12.50",
"currency": "GHS",
"formatted": "GH\u20b512.50"
},
"tax": {
"amount": "0.00",
"currency": "GHS",
"formatted": "GH\u20b50.00"
}
}
],
"applications": [
{
"id": 1,
"type": "invoice",
"amount": {
"amount": "12.50",
"currency": "GHS",
"formatted": "GH\u20b512.50"
},
"applied_at": "2026-10-04T12: 00: 00+00: 00"
}
],
"refund_plan": {
"available": false,
"requires_gateway": false,
"maximum": {
"amount": "0.00",
"currency": "GHS",
"formatted": "GH\u20b50.00"
},
"methods": [
{
"id": 1,
"name": "Cash",
"code": "cash"
}
]
},
"allowed_actions": {
"submit": false,
"approve": false,
"reject": false,
"void": false,
"apply": false,
"refund": false
},
"source_workflow": "invoice",
"unavailable_reason": null,
"source_sale": null
}
}
}
{
"success": false,
"code": "validation_failed",
"message": "The given data was invalid.",
"errors": {
"store_id": [
"Select an accessible active store."
]
}
}
Requires current after-sales capability and credit_notes.view plus action authority. Whole source invoice, note and target scopes are checked before serialization/pagination. Permanent UUID replay reauthorizes current result ownership before mutable revision/state checks. No provider calls, client approver/payable/tax/totals or original serial override. Cache-Control: private, no-store. Manual refund acknowledges a ledger operation, never a gateway transfer. Shared web/native writers lock actor, sorted source/target invoice roots, note and stock/payment inputs before reauthorizing. Unknown fields rejected.
| Parameter | In | Type | Description |
|---|---|---|---|
| creditNote *required | path | string | Path parameter: Credit Note |
| store_id *required | body | integer | Selected accessible active store. |
| warehouse_id | body | integer | Optional selected accessible active warehouse. |
| operation_key *required | body | string | Permanent UUID. |
| revision *required | body | string | Current 64-character source Invoice revision for creation; CreditNote revision for all actions. |
{
"store_id": 1,
"warehouse_id": 1,
"operation_key": "00000000-0000-4000-8000-000000000001",
"revision": "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"
}
{
"success": true,
"data": {
"operation": {
"key": "00000000-0000-4000-8000-000000000001",
"replayed": false
},
"result_ids": {
"credit_note_id": 1
},
"resource": {
"kind": "credit_note",
"id": 1,
"invoice_id": 1,
"number": "CN-202610-0001",
"store_id": 1,
"warehouse_id": 1,
"type": "return",
"status": "approved",
"revision": "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa",
"customer": {
"id": 1,
"name": "Stored Customer"
},
"subtotal": {
"amount": "12.50",
"currency": "GHS",
"formatted": "GH\u20b512.50"
},
"tax": {
"amount": "0.00",
"currency": "GHS",
"formatted": "GH\u20b50.00"
},
"total": {
"amount": "12.50",
"currency": "GHS",
"formatted": "GH\u20b512.50"
},
"consumed": {
"amount": "0.00",
"currency": "GHS",
"formatted": "GH\u20b50.00"
},
"applied": {
"amount": "0.00",
"currency": "GHS",
"formatted": "GH\u20b50.00"
},
"refunded": {
"amount": "0.00",
"currency": "GHS",
"formatted": "GH\u20b50.00"
},
"remaining": {
"amount": "12.50",
"currency": "GHS",
"formatted": "GH\u20b512.50"
},
"reason": "Example credit",
"voided_reason": null,
"requires_approval": true,
"created_at": "2026-10-04T12: 00: 00+00: 00",
"approved_at": null,
"voided_at": null,
"items": [
{
"id": 1,
"invoice_item_id": 1,
"name": "Stored Product",
"sku": "STORED-SKU",
"warehouse_id": 1,
"quantity": "1.000",
"base_quantity": "1.000000",
"unit": {
"id": 1,
"name": "pc"
},
"serial_number": null,
"condition": "resellable",
"restock": true,
"restocked": false,
"amount": {
"amount": "12.50",
"currency": "GHS",
"formatted": "GH\u20b512.50"
},
"tax": {
"amount": "0.00",
"currency": "GHS",
"formatted": "GH\u20b50.00"
}
}
],
"applications": [],
"refund_plan": {
"available": false,
"requires_gateway": false,
"maximum": {
"amount": "0.00",
"currency": "GHS",
"formatted": "GH\u20b50.00"
},
"methods": [
{
"id": 1,
"name": "Cash",
"code": "cash"
}
]
},
"allowed_actions": {
"submit": false,
"approve": false,
"reject": false,
"void": true,
"apply": true,
"refund": false
},
"source_workflow": "invoice",
"unavailable_reason": null,
"source_sale": null
}
}
}
{
"success": false,
"code": "validation_failed",
"message": "The given data was invalid.",
"errors": {
"store_id": [
"Select an accessible active store."
]
}
}
Requires current after-sales capability and credit_notes.view plus action authority. Whole source invoice, note and target scopes are checked before serialization/pagination. Permanent UUID replay reauthorizes current result ownership before mutable revision/state checks. No provider calls, client approver/payable/tax/totals or original serial override. Cache-Control: private, no-store. Only same non-null authoritative customer, standalone issued sent/partial/overdue targets with positive balance. Sale-linked sources and targets are excluded. Full record scope and target view policy apply before default25/max50 pagination. Picker is not write authority.
| Parameter | In | Type | Description |
|---|---|---|---|
| creditNote *required | path | string | Path parameter: Credit Note |
| store_id *required | query | integer | Selected accessible active store. |
| warehouse_id | query | integer | Optional selected accessible active warehouse. |
| q | query | string | Literal number search, max100. |
| page | query | integer | Page, minimum1. |
| per_page | query | integer | Default25, maximum50. |
{
"store_id": 1,
"warehouse_id": 1,
"q": "example",
"page": 1,
"per_page": 1
}
{
"success": true,
"data": {
"items": [
{
"id": 1,
"number": "INV-202610-0001",
"balance": {
"amount": "12.50",
"currency": "GHS",
"formatted": "GH\u20b512.50"
},
"revision": "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"
}
]
},
"meta": {
"current_page": 1,
"last_page": 1,
"per_page": 25,
"total": 1
}
}
{
"success": false,
"code": "validation_failed",
"message": "The given data was invalid.",
"errors": {
"store_id": [
"Select an accessible active store."
]
}
}
Requires current after-sales capability and credit_notes.view plus action authority. Whole source invoice, note and target scopes are checked before serialization/pagination. Permanent UUID replay reauthorizes current result ownership before mutable revision/state checks. No provider calls, client approver/payable/tax/totals or original serial override. Cache-Control: private, no-store. Manual refund acknowledges a ledger operation, never a gateway transfer. Shared web/native writers lock actor, sorted source/target invoice roots, note and stock/payment inputs before reauthorizing. Unknown fields rejected. Requires a single-use X-Staff-Step-Up confirmation from auth/step-up when step-up enforcement and device security are on; a missing confirmation then returns 428 step_up_required, and an invalid one always does.
| Parameter | In | Type | Description |
|---|---|---|---|
| creditNote *required | path | string | Path parameter: Credit Note |
| store_id *required | body | integer | Selected accessible active store. |
| warehouse_id | body | integer | Optional selected accessible active warehouse. |
| operation_key *required | body | string | Permanent UUID. |
| revision *required | body | string | Current 64-character source Invoice revision for creation; CreditNote revision for all actions. |
| amount *required | body | string | Positive decimal Money intent, max10 integer and2 fractional digits. |
| notes | body | string | Maximum2000 characters; never persisted in operation marker. |
| payment_method_id *required | body | integer | Current active store-enabled genuinely manual payout method. |
| refund_confirmed *required | body | boolean | Actual manual refund acknowledgement. |
{
"store_id": 1,
"warehouse_id": 1,
"operation_key": "00000000-0000-4000-8000-000000000001",
"revision": "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa",
"amount": "12.50",
"payment_method_id": 1,
"refund_confirmed": true
}
{
"success": true,
"data": {
"operation": {
"key": "00000000-0000-4000-8000-000000000001",
"replayed": false
},
"result_ids": {
"credit_note_id": 1,
"credit_note_application_id": 1
},
"resource": {
"kind": "credit_note",
"id": 1,
"invoice_id": 1,
"number": "CN-202610-0001",
"store_id": 1,
"warehouse_id": 1,
"type": "return",
"status": "applied",
"revision": "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa",
"customer": {
"id": 1,
"name": "Stored Customer"
},
"subtotal": {
"amount": "12.50",
"currency": "GHS",
"formatted": "GH\u20b512.50"
},
"tax": {
"amount": "0.00",
"currency": "GHS",
"formatted": "GH\u20b50.00"
},
"total": {
"amount": "12.50",
"currency": "GHS",
"formatted": "GH\u20b512.50"
},
"consumed": {
"amount": "12.50",
"currency": "GHS",
"formatted": "GH\u20b512.50"
},
"applied": {
"amount": "0.00",
"currency": "GHS",
"formatted": "GH\u20b50.00"
},
"refunded": {
"amount": "12.50",
"currency": "GHS",
"formatted": "GH\u20b512.50"
},
"remaining": {
"amount": "0.00",
"currency": "GHS",
"formatted": "GH\u20b50.00"
},
"reason": "Example credit",
"voided_reason": null,
"requires_approval": true,
"created_at": "2026-10-04T12: 00: 00+00: 00",
"approved_at": null,
"voided_at": null,
"items": [
{
"id": 1,
"invoice_item_id": 1,
"name": "Stored Product",
"sku": "STORED-SKU",
"warehouse_id": 1,
"quantity": "1.000",
"base_quantity": "1.000000",
"unit": {
"id": 1,
"name": "pc"
},
"serial_number": null,
"condition": "resellable",
"restock": true,
"restocked": false,
"amount": {
"amount": "12.50",
"currency": "GHS",
"formatted": "GH\u20b512.50"
},
"tax": {
"amount": "0.00",
"currency": "GHS",
"formatted": "GH\u20b50.00"
}
}
],
"applications": [
{
"id": 1,
"type": "refund",
"amount": {
"amount": "12.50",
"currency": "GHS",
"formatted": "GH\u20b512.50"
},
"applied_at": "2026-10-04T12: 00: 00+00: 00"
}
],
"refund_plan": {
"available": false,
"requires_gateway": false,
"maximum": {
"amount": "0.00",
"currency": "GHS",
"formatted": "GH\u20b50.00"
},
"methods": [
{
"id": 1,
"name": "Cash",
"code": "cash"
}
]
},
"allowed_actions": {
"submit": false,
"approve": false,
"reject": false,
"void": false,
"apply": false,
"refund": false
},
"source_workflow": "invoice",
"unavailable_reason": null,
"source_sale": null
}
}
}
{
"success": false,
"code": "validation_failed",
"message": "The given data was invalid.",
"errors": {
"store_id": [
"Select an accessible active store."
]
}
}
Requires current after-sales capability and credit_notes.view plus action authority. Whole source invoice, note and target scopes are checked before serialization/pagination. Permanent UUID replay reauthorizes current result ownership before mutable revision/state checks. No provider calls, client approver/payable/tax/totals or original serial override. Cache-Control: private, no-store. Manual refund acknowledges a ledger operation, never a gateway transfer. Shared web/native writers lock actor, sorted source/target invoice roots, note and stock/payment inputs before reauthorizing. Unknown fields rejected.
| Parameter | In | Type | Description |
|---|---|---|---|
| creditNote *required | path | string | Path parameter: Credit Note |
| store_id *required | body | integer | Selected accessible active store. |
| warehouse_id | body | integer | Optional selected accessible active warehouse. |
| operation_key *required | body | string | Permanent UUID. |
| revision *required | body | string | Current 64-character source Invoice revision for creation; CreditNote revision for all actions. |
| reason *required | body | string | Maximum2000 characters. |
{
"store_id": 1,
"warehouse_id": 1,
"operation_key": "00000000-0000-4000-8000-000000000001",
"revision": "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa",
"reason": "Example reason"
}
{
"success": true,
"data": {
"operation": {
"key": "00000000-0000-4000-8000-000000000001",
"replayed": false
},
"result_ids": {
"credit_note_id": 1
},
"resource": {
"kind": "credit_note",
"id": 1,
"invoice_id": 1,
"number": "CN-202610-0001",
"store_id": 1,
"warehouse_id": 1,
"type": "return",
"status": "voided",
"revision": "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa",
"customer": {
"id": 1,
"name": "Stored Customer"
},
"subtotal": {
"amount": "12.50",
"currency": "GHS",
"formatted": "GH\u20b512.50"
},
"tax": {
"amount": "0.00",
"currency": "GHS",
"formatted": "GH\u20b50.00"
},
"total": {
"amount": "12.50",
"currency": "GHS",
"formatted": "GH\u20b512.50"
},
"consumed": {
"amount": "0.00",
"currency": "GHS",
"formatted": "GH\u20b50.00"
},
"applied": {
"amount": "0.00",
"currency": "GHS",
"formatted": "GH\u20b50.00"
},
"refunded": {
"amount": "0.00",
"currency": "GHS",
"formatted": "GH\u20b50.00"
},
"remaining": {
"amount": "12.50",
"currency": "GHS",
"formatted": "GH\u20b512.50"
},
"reason": "Example credit",
"voided_reason": null,
"requires_approval": true,
"created_at": "2026-10-04T12: 00: 00+00: 00",
"approved_at": null,
"voided_at": null,
"items": [
{
"id": 1,
"invoice_item_id": 1,
"name": "Stored Product",
"sku": "STORED-SKU",
"warehouse_id": 1,
"quantity": "1.000",
"base_quantity": "1.000000",
"unit": {
"id": 1,
"name": "pc"
},
"serial_number": null,
"condition": "resellable",
"restock": true,
"restocked": false,
"amount": {
"amount": "12.50",
"currency": "GHS",
"formatted": "GH\u20b512.50"
},
"tax": {
"amount": "0.00",
"currency": "GHS",
"formatted": "GH\u20b50.00"
}
}
],
"applications": [],
"refund_plan": {
"available": false,
"requires_gateway": false,
"maximum": {
"amount": "0.00",
"currency": "GHS",
"formatted": "GH\u20b50.00"
},
"methods": [
{
"id": 1,
"name": "Cash",
"code": "cash"
}
]
},
"allowed_actions": {
"submit": false,
"approve": false,
"reject": false,
"void": false,
"apply": false,
"refund": false
},
"source_workflow": "invoice",
"unavailable_reason": null,
"source_sale": null
}
}
}
{
"success": false,
"code": "validation_failed",
"message": "The given data was invalid.",
"errors": {
"store_id": [
"Select an accessible active store."
]
}
}
Requires current after-sales capability and credit_notes.view plus action authority. Whole source invoice, note and target scopes are checked before serialization/pagination. Permanent UUID replay reauthorizes current result ownership before mutable revision/state checks. No provider calls, client approver/payable/tax/totals or original serial override. Cache-Control: private, no-store. Manual refund acknowledges a ledger operation, never a gateway transfer. Shared web/native writers lock actor, sorted source/target invoice roots, note and stock/payment inputs before reauthorizing. Unknown fields rejected.
| Parameter | In | Type | Description |
|---|---|---|---|
| creditNote *required | path | string | Path parameter: Credit Note |
| store_id *required | body | integer | Selected accessible active store. |
| warehouse_id | body | integer | Optional selected accessible active warehouse. |
| operation_key *required | body | string | Permanent UUID. |
| revision *required | body | string | Current 64-character source Invoice revision for creation; CreditNote revision for all actions. |
{
"store_id": 1,
"warehouse_id": 1,
"operation_key": "00000000-0000-4000-8000-000000000001",
"revision": "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"
}
{
"success": true,
"data": {
"operation": {
"key": "00000000-0000-4000-8000-000000000001",
"replayed": false
},
"result_ids": {
"credit_note_id": 1
},
"resource": {
"kind": "credit_note",
"id": 1,
"invoice_id": 1,
"number": "CN-202610-0001",
"store_id": 1,
"warehouse_id": 1,
"type": "return",
"status": "pending_approval",
"revision": "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa",
"customer": {
"id": 1,
"name": "Stored Customer"
},
"subtotal": {
"amount": "12.50",
"currency": "GHS",
"formatted": "GH\u20b512.50"
},
"tax": {
"amount": "0.00",
"currency": "GHS",
"formatted": "GH\u20b50.00"
},
"total": {
"amount": "12.50",
"currency": "GHS",
"formatted": "GH\u20b512.50"
},
"consumed": {
"amount": "0.00",
"currency": "GHS",
"formatted": "GH\u20b50.00"
},
"applied": {
"amount": "0.00",
"currency": "GHS",
"formatted": "GH\u20b50.00"
},
"refunded": {
"amount": "0.00",
"currency": "GHS",
"formatted": "GH\u20b50.00"
},
"remaining": {
"amount": "12.50",
"currency": "GHS",
"formatted": "GH\u20b512.50"
},
"reason": "Example credit",
"voided_reason": null,
"requires_approval": true,
"created_at": "2026-10-04T12: 00: 00+00: 00",
"approved_at": null,
"voided_at": null,
"items": [
{
"id": 1,
"invoice_item_id": 1,
"name": "Stored Product",
"sku": "STORED-SKU",
"warehouse_id": 1,
"quantity": "1.000",
"base_quantity": "1.000000",
"unit": {
"id": 1,
"name": "pc"
},
"serial_number": null,
"condition": "resellable",
"restock": true,
"restocked": false,
"amount": {
"amount": "12.50",
"currency": "GHS",
"formatted": "GH\u20b512.50"
},
"tax": {
"amount": "0.00",
"currency": "GHS",
"formatted": "GH\u20b50.00"
}
}
],
"applications": [],
"refund_plan": {
"available": false,
"requires_gateway": false,
"maximum": {
"amount": "0.00",
"currency": "GHS",
"formatted": "GH\u20b50.00"
},
"methods": [
{
"id": 1,
"name": "Cash",
"code": "cash"
}
]
},
"allowed_actions": {
"submit": false,
"approve": true,
"reject": true,
"void": true,
"apply": false,
"refund": false
},
"source_workflow": "invoice",
"unavailable_reason": null,
"source_sale": null
}
}
}
{
"success": false,
"code": "validation_failed",
"message": "The given data was invalid.",
"errors": {
"store_id": [
"Select an accessible active store."
]
}
}
Requires current after-sales capability and credit_notes.view plus action authority. Whole source invoice, note and target scopes are checked before serialization/pagination. Permanent UUID replay reauthorizes current result ownership before mutable revision/state checks. No provider calls, client approver/payable/tax/totals or original serial override. Cache-Control: private, no-store. Manual refund acknowledges a ledger operation, never a gateway transfer. Shared web/native writers lock actor, sorted source/target invoice roots, note and stock/payment inputs before reauthorizing. Unknown fields rejected. Requires a single-use X-Staff-Step-Up confirmation from auth/step-up when step-up enforcement and device security are on; a missing confirmation then returns 428 step_up_required, and an invalid one always does.
| Parameter | In | Type | Description |
|---|---|---|---|
| creditNote *required | path | string | Path parameter: Credit Note |
| store_id *required | body | integer | Selected accessible active store. |
| warehouse_id | body | integer | Optional selected accessible active warehouse. |
| operation_key *required | body | string | Permanent UUID. |
| revision *required | body | string | Current 64-character source Invoice revision for creation; CreditNote revision for all actions. |
| reason *required | body | string | Maximum2000 characters. |
{
"store_id": 1,
"warehouse_id": 1,
"operation_key": "00000000-0000-4000-8000-000000000001",
"revision": "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa",
"reason": "Example reason"
}
{
"success": true,
"data": {
"operation": {
"key": "00000000-0000-4000-8000-000000000001",
"replayed": false
},
"result_ids": {
"credit_note_id": 1
},
"resource": {
"kind": "credit_note",
"id": 1,
"invoice_id": 1,
"number": "CN-202610-0001",
"store_id": 1,
"warehouse_id": 1,
"type": "return",
"status": "voided",
"revision": "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa",
"customer": {
"id": 1,
"name": "Stored Customer"
},
"subtotal": {
"amount": "12.50",
"currency": "GHS",
"formatted": "GH\u20b512.50"
},
"tax": {
"amount": "0.00",
"currency": "GHS",
"formatted": "GH\u20b50.00"
},
"total": {
"amount": "12.50",
"currency": "GHS",
"formatted": "GH\u20b512.50"
},
"consumed": {
"amount": "0.00",
"currency": "GHS",
"formatted": "GH\u20b50.00"
},
"applied": {
"amount": "0.00",
"currency": "GHS",
"formatted": "GH\u20b50.00"
},
"refunded": {
"amount": "0.00",
"currency": "GHS",
"formatted": "GH\u20b50.00"
},
"remaining": {
"amount": "12.50",
"currency": "GHS",
"formatted": "GH\u20b512.50"
},
"reason": "Example credit",
"voided_reason": null,
"requires_approval": true,
"created_at": "2026-10-04T12: 00: 00+00: 00",
"approved_at": null,
"voided_at": null,
"items": [
{
"id": 1,
"invoice_item_id": 1,
"name": "Stored Product",
"sku": "STORED-SKU",
"warehouse_id": 1,
"quantity": "1.000",
"base_quantity": "1.000000",
"unit": {
"id": 1,
"name": "pc"
},
"serial_number": null,
"condition": "resellable",
"restock": true,
"restocked": false,
"amount": {
"amount": "12.50",
"currency": "GHS",
"formatted": "GH\u20b512.50"
},
"tax": {
"amount": "0.00",
"currency": "GHS",
"formatted": "GH\u20b50.00"
}
}
],
"applications": [],
"refund_plan": {
"available": false,
"requires_gateway": false,
"maximum": {
"amount": "0.00",
"currency": "GHS",
"formatted": "GH\u20b50.00"
},
"methods": [
{
"id": 1,
"name": "Cash",
"code": "cash"
}
]
},
"allowed_actions": {
"submit": false,
"approve": false,
"reject": false,
"void": false,
"apply": false,
"refund": false
},
"source_workflow": "invoice",
"unavailable_reason": null,
"source_sale": null
}
}
}
{
"success": false,
"code": "validation_failed",
"message": "The given data was invalid.",
"errors": {
"store_id": [
"Select an accessible active store."
]
}
}
Global minimal contact profile, requires after-sales feature and customers.view, not POS/invoice capability or warehouse ownership. No purchase aggregates, groups or notes. Existing POS/invoice/estimate workflow lookup gates remain unchanged. Customers-management create/edit uses workflow=customers with customers capability and customers.create/edit through the permanent runner. Private no-store.
| Parameter | In | Type | Description |
|---|---|---|---|
| q | query | string | Literal number or stored/live customer-name search, maximum 100 characters. |
| page | query | integer | Page >= 1. |
| per_page | query | integer | Default 25, maximum 50; invalid values return 422. |
{
"q": "example",
"page": 1,
"per_page": 1
}
{
"success": true,
"data": {
"items": [
{
"kind": "customer",
"id": 1,
"revision": "opaque-example-revision",
"name": "Example Customer",
"email": "customer@example.invalid",
"phone": null,
"address": null,
"city": null,
"state": null,
"postal_code": null,
"country": null
}
]
},
"meta": {
"current_page": 1,
"last_page": 1,
"per_page": 25,
"total": 1
}
}
{
"success": false,
"code": "validation_failed",
"message": "The given data was invalid.",
"errors": {
"store_id": [
"Select an accessible active store."
]
}
}
Global customer contact DTO, never location-owned. No sales, orders, totals, purchase activity, customer groups or private notes are exposed. POS lookup requires POS feature enabled and pos.access; it does not require the after-sales customers capability. Mutations require customers.create/edit plus the selected workflow feature and permissions: POS enabled with pos.access, or invoices enabled with invoices.view. Selected location is retained only as operation context, never customer ownership. Unknown/authority fields are rejected. Permanent actions are pos.customer.create/update and invoice.customer.create/update; ledger stores only customer_id, action and payload hash, not contact data. Resolve rechecks the original workflow gate and permission and returns the fresh minimal DTO. Estimate workflow requires invoices feature enabled and estimates.view instead of POS/invoice permissions, plus the same customers.create/edit. Its exact durable actions are estimate.customer.create/update with unchanged UUID, email uniqueness and fresh replay rules. Customers workflow requires current after-sales customers capability and customers.create/edit, independent of POS/invoice permissions. Permanent actions customers.customer.create and customers.customer.update retain only customer_id; no contact payload persistence. Replay/reconciliation checks the original live workflow.
| Parameter | In | Type | Description |
|---|---|---|---|
| name *required | body | string | Contact name, maximum 255 characters. |
| body | string | Valid globally unique email, maximum 255 characters. | |
| phone | body | string | Maximum 50 characters. |
| address | body | string | Maximum 500 characters. |
| city | body | string | Maximum 100 characters. |
| state | body | string | Maximum 100 characters. |
| postal_code | body | string | Maximum 20 characters. |
| country | body | string | Maximum 100 characters. |
| workflow *required | body | string | Server-owned workflow: pos, invoice, estimate or customers; each requires its current capability and customer action permission. |
| store_id *required | body | integer | Selected accessible active store. |
| warehouse_id | body | integer | Optional selected accessible active warehouse. |
| operation_key *required | body | string | Permanent actor-scoped UUID; identical committed replay precedes revision and email uniqueness checks. |
{
"store_id": 1,
"workflow": "pos",
"operation_key": "00000000-0000-4000-8000-000000000001",
"name": "Example Customer"
}
{
"success": true,
"data": {
"operation": {
"key": "00000000-0000-4000-8000-000000000001",
"replayed": false
},
"result_ids": {
"customer_id": 1
},
"resource": {
"kind": "customer",
"id": 1,
"revision": "opaque-example-revision",
"name": "Example Customer",
"email": "customer@example.invalid",
"phone": null,
"address": null,
"city": null,
"state": null,
"postal_code": null,
"country": null
}
}
}
{
"success": false,
"code": "validation_failed",
"message": "The given data was invalid.",
"errors": {
"store_id": [
"Select an accessible active store."
]
}
}
Global customer soft deletion under actor/customer row locks, fresh existing delete policy and revision. Walk-in and any linked sales/invoices/estimates including soft-deleted financial records are protected with a neutral error; no linked IDs disclosed and no historical sale deleted. Revision-only nonfinancial deletion matches the existing staff draft-delete contract; not a financial operation/replay route.
| Parameter | In | Type | Description |
|---|---|---|---|
| customer *required | path | string | Path parameter: Customer |
| revision *required | body | string | Fresh opaque customer revision, maximum 255 characters. |
{
"revision": "example"
}
{
"success": true,
"data": {
"deleted": true
}
}
{
"success": false,
"code": "validation_failed",
"message": "The given data was invalid.",
"errors": {
"store_id": [
"Select an accessible active store."
]
}
}
Global minimal contact profile, requires after-sales feature and customers.view, not POS/invoice capability or warehouse ownership. No purchase aggregates, groups or notes. Existing POS/invoice/estimate workflow lookup gates remain unchanged. Customers-management create/edit uses workflow=customers with customers capability and customers.create/edit through the permanent runner. Private no-store.
| Parameter | In | Type | Description |
|---|---|---|---|
| customer *required | path | string | Path parameter: Customer |
[]
{
"success": true,
"data": {
"kind": "customer",
"id": 1,
"revision": "opaque-example-revision",
"name": "Example Customer",
"email": "customer@example.invalid",
"phone": null,
"address": null,
"city": null,
"state": null,
"postal_code": null,
"country": null
}
}
{
"success": false,
"code": "validation_failed",
"message": "The given data was invalid.",
"errors": {
"store_id": [
"Select an accessible active store."
]
}
}
Global customer contact DTO, never location-owned. No sales, orders, totals, purchase activity, customer groups or private notes are exposed. POS lookup requires POS feature enabled and pos.access; it does not require the after-sales customers capability. Mutations require customers.create/edit plus the selected workflow feature and permissions: POS enabled with pos.access, or invoices enabled with invoices.view. Selected location is retained only as operation context, never customer ownership. Unknown/authority fields are rejected. Permanent actions are pos.customer.create/update and invoice.customer.create/update; ledger stores only customer_id, action and payload hash, not contact data. Resolve rechecks the original workflow gate and permission and returns the fresh minimal DTO. Estimate workflow requires invoices feature enabled and estimates.view instead of POS/invoice permissions, plus the same customers.create/edit. Its exact durable actions are estimate.customer.create/update with unchanged UUID, email uniqueness and fresh replay rules. Customers workflow requires current after-sales customers capability and customers.create/edit, independent of POS/invoice permissions. Permanent actions customers.customer.create and customers.customer.update retain only customer_id; no contact payload persistence. Replay/reconciliation checks the original live workflow.
| Parameter | In | Type | Description |
|---|---|---|---|
| customer *required | path | string | Path parameter: Customer |
| name | body | string | Contact name, maximum 255 characters. |
| body | string | Valid globally unique email, maximum 255 characters. | |
| phone | body | string | Maximum 50 characters. |
| address | body | string | Maximum 500 characters. |
| city | body | string | Maximum 100 characters. |
| state | body | string | Maximum 100 characters. |
| postal_code | body | string | Maximum 20 characters. |
| country | body | string | Maximum 100 characters. |
| workflow *required | body | string | Server-owned workflow: pos, invoice, estimate or customers; each requires its current capability and customer action permission. |
| store_id *required | body | integer | Selected accessible active store. |
| warehouse_id | body | integer | Optional selected accessible active warehouse. |
| operation_key *required | body | string | Permanent actor-scoped UUID; identical committed replay precedes revision and email uniqueness checks. |
| revision *required | body | string | Fresh customer revision; stale intent returns 409. |
{
"store_id": 1,
"workflow": "pos",
"operation_key": "00000000-0000-4000-8000-000000000001",
"name": "Example Customer",
"revision": "opaque-example-revision"
}
{
"success": true,
"data": {
"operation": {
"key": "00000000-0000-4000-8000-000000000001",
"replayed": false
},
"result_ids": {
"customer_id": 1
},
"resource": {
"kind": "customer",
"id": 1,
"revision": "opaque-example-revision",
"name": "Example Customer",
"email": "customer@example.invalid",
"phone": null,
"address": null,
"city": null,
"state": null,
"postal_code": null,
"country": null
}
}
}
{
"success": false,
"code": "validation_failed",
"message": "The given data was invalid.",
"errors": {
"store_id": [
"Select an accessible active store."
]
}
}
Global minimal contact profile, requires after-sales feature and customers.view, not POS/invoice capability or warehouse ownership. No purchase aggregates, groups or notes. Existing POS/invoice/estimate workflow lookup gates remain unchanged. Customers-management create/edit uses workflow=customers with customers capability and customers.create/edit through the permanent runner. Private no-store. Transaction history uses selected location, independently checks every source permission/capability and full record scope before pagination. Customer ID from path cannot be overridden by a query filter. Same nonduplicating POS/Web Order/issued-invoice history only; not an estimate/return/credit revenue summary.
| Parameter | In | Type | Description |
|---|---|---|---|
| customer *required | path | string | Path parameter: Customer |
| store_id *required | query | integer | Selected accessible active store. |
| warehouse_id | query | integer | Optional selected accessible active warehouse. |
| q | query | string | Literal number or stored/live customer-name search, maximum 100 characters. |
| source | query | string | all (default), pos, invoice, web_order. |
| status | query | string | pending, completed, refunded, voided, sent, partial, paid, overdue. |
| customer_id | query | integer | Optional global customer filter; no contact authority. |
| date_from | query | string | Africa/Accra calendar date YYYY-MM-DD inclusive. |
| date_to | query | string | Inclusive YYYY-MM-DD, not before date_from. |
| page | query | integer | Page >= 1. |
| per_page | query | integer | Default 25, maximum 50; invalid values return 422. |
{
"store_id": 1,
"warehouse_id": 1,
"q": "example",
"source": "example",
"status": "example",
"customer_id": 1,
"date_from": "example",
"date_to": "example",
"page": 1,
"per_page": 1
}
{
"success": true,
"data": {
"items": [
{
"kind": "sale",
"id": 1,
"source": "pos",
"source_label": "POS",
"number": "RCP-EXAMPLE-001",
"status": "completed",
"store_id": 1,
"warehouse_id": 1,
"created_at": "2026-10-04T12: 00: 00+00: 00",
"customer": {
"id": 1,
"name": "Stored Customer",
"available": true
},
"total": {
"amount": "12.50",
"currency": "GHS",
"formatted": "GH\u20b512.50"
},
"link": {
"kind": "sale",
"id": 1
}
},
{
"kind": "invoice",
"id": 1,
"source": "invoice",
"source_label": "Invoice",
"number": "INV-EXAMPLE-001",
"status": "sent",
"store_id": 1,
"warehouse_id": 1,
"created_at": "2026-10-04T12: 00: 00+00: 00",
"customer": {
"id": 1,
"name": "Stored Customer",
"available": true
},
"total": {
"amount": "12.50",
"currency": "GHS",
"formatted": "GH\u20b512.50"
},
"link": {
"kind": "invoice",
"id": 1
}
}
]
},
"meta": {
"current_page": 1,
"last_page": 1,
"per_page": 25,
"total": 2
}
}
{
"success": false,
"code": "validation_failed",
"message": "The given data was invalid.",
"errors": {
"store_id": [
"Select an accessible active store."
]
}
}
Authenticated private invoice/estimate/payment document. Requires invoices feature and live invoices.view or estimates.view as appropriate, existing view policy and complete parent/line/linked-record scope. No POS/payment permission prerequisite. Invoice payment IDs must belong to the exact nested invoice and original queued parent; deleted, missing or reparented children fail closed on request/replay/worker. Existing branding-aware templates and snapshots are reused. PDF response is original binary, private/no-store. Email is an independent permanent document operation, never a financial retry or state transition. Encrypted recipient is absent from result/operation/job payload; job contains only delivery ID. Enqueue failure stays pending; same-key retry safely queues that row once. Stale queued rows may be recovered; interrupted processing becomes uncertain, never automatic resend. accepted means provider acceptance, not inbox delivery; log/array transports fail. GET operations/{key} supplies current authorized delivery state. No client kind, URL, TTL, share key or provider authority.
| Parameter | In | Type | Description |
|---|---|---|---|
| estimate *required | path | string | Path parameter: Estimate |
| store_id *required | body | integer | Selected accessible active store. |
| warehouse_id | body | integer | Optional selected accessible active warehouse. |
| recipient *required | body | string | Validated RFC email, maximum 254 characters; encrypted at rest, omitted from all returned data. |
| operation_key *required | body | string | Permanent UUID for document.estimate.email. Identical retry returns only original delivery ID, never repeats invoice/payment/stock. |
{
"store_id": 1,
"warehouse_id": 1,
"recipient": "customer@example.invalid",
"operation_key": "00000000-0000-4000-8000-000000000001"
}
{
"success": true,
"data": {
"operation": {
"key": "00000000-0000-4000-8000-000000000001",
"replayed": false
},
"result_ids": {
"document_delivery_id": 1
},
"resource": {
"kind": "document_delivery",
"id": 1,
"document_kind": "estimate",
"document_id": 1,
"status": "queued",
"queued_at": "2026-10-04T12: 00: 00+00: 00",
"attempted_at": null,
"accepted_at": null,
"error_code": null
}
}
}
{
"success": false,
"code": "validation_failed",
"message": "The given data was invalid.",
"errors": {
"store_id": [
"Select an accessible active store."
]
}
}
Authenticated private invoice/estimate/payment document. Requires invoices feature and live invoices.view or estimates.view as appropriate, existing view policy and complete parent/line/linked-record scope. No POS/payment permission prerequisite. Invoice payment IDs must belong to the exact nested invoice and original queued parent; deleted, missing or reparented children fail closed on request/replay/worker. Existing branding-aware templates and snapshots are reused. PDF response is original binary, private/no-store. Email is an independent permanent document operation, never a financial retry or state transition. Encrypted recipient is absent from result/operation/job payload; job contains only delivery ID. Enqueue failure stays pending; same-key retry safely queues that row once. Stale queued rows may be recovered; interrupted processing becomes uncertain, never automatic resend. accepted means provider acceptance, not inbox delivery; log/array transports fail. GET operations/{key} supplies current authorized delivery state. No client kind, URL, TTL, share key or provider authority.
| Parameter | In | Type | Description |
|---|---|---|---|
| estimate *required | path | string | Path parameter: Estimate |
| store_id *required | query | integer | Selected accessible active store. |
| warehouse_id | query | integer | Optional selected accessible active warehouse. |
{
"store_id": 1,
"warehouse_id": 1
}
"%PDF-1.7 (synthetic binary example)"
{
"success": false,
"code": "validation_failed",
"message": "The given data was invalid.",
"errors": {
"store_id": [
"Select an accessible active store."
]
}
}
Authenticated private invoice/estimate/payment document. Requires invoices feature and live invoices.view or estimates.view as appropriate, existing view policy and complete parent/line/linked-record scope. No POS/payment permission prerequisite. Invoice payment IDs must belong to the exact nested invoice and original queued parent; deleted, missing or reparented children fail closed on request/replay/worker. Existing branding-aware templates and snapshots are reused. PDF response is original binary, private/no-store. Email is an independent permanent document operation, never a financial retry or state transition. Encrypted recipient is absent from result/operation/job payload; job contains only delivery ID. Enqueue failure stays pending; same-key retry safely queues that row once. Stale queued rows may be recovered; interrupted processing becomes uncertain, never automatic resend. accepted means provider acceptance, not inbox delivery; log/array transports fail. GET operations/{key} supplies current authorized delivery state. No client kind, URL, TTL, share key or provider authority.
| Parameter | In | Type | Description |
|---|---|---|---|
| invoice *required | path | string | Path parameter: Invoice |
| store_id *required | body | integer | Selected accessible active store. |
| warehouse_id | body | integer | Optional selected accessible active warehouse. |
| recipient *required | body | string | Validated RFC email, maximum 254 characters; encrypted at rest, omitted from all returned data. |
| operation_key *required | body | string | Permanent UUID for document.invoice.email. Identical retry returns only original delivery ID, never repeats invoice/payment/stock. |
{
"store_id": 1,
"warehouse_id": 1,
"recipient": "customer@example.invalid",
"operation_key": "00000000-0000-4000-8000-000000000001"
}
{
"success": true,
"data": {
"operation": {
"key": "00000000-0000-4000-8000-000000000001",
"replayed": false
},
"result_ids": {
"document_delivery_id": 1
},
"resource": {
"kind": "document_delivery",
"id": 1,
"document_kind": "invoice",
"document_id": 1,
"status": "queued",
"queued_at": "2026-10-04T12: 00: 00+00: 00",
"attempted_at": null,
"accepted_at": null,
"error_code": null
}
}
}
{
"success": false,
"code": "validation_failed",
"message": "The given data was invalid.",
"errors": {
"store_id": [
"Select an accessible active store."
]
}
}
Authenticated private invoice/estimate/payment document. Requires invoices feature and live invoices.view or estimates.view as appropriate, existing view policy and complete parent/line/linked-record scope. No POS/payment permission prerequisite. Invoice payment IDs must belong to the exact nested invoice and original queued parent; deleted, missing or reparented children fail closed on request/replay/worker. Existing branding-aware templates and snapshots are reused. PDF response is original binary, private/no-store. Email is an independent permanent document operation, never a financial retry or state transition. Encrypted recipient is absent from result/operation/job payload; job contains only delivery ID. Enqueue failure stays pending; same-key retry safely queues that row once. Stale queued rows may be recovered; interrupted processing becomes uncertain, never automatic resend. accepted means provider acceptance, not inbox delivery; log/array transports fail. GET operations/{key} supplies current authorized delivery state. No client kind, URL, TTL, share key or provider authority.
| Parameter | In | Type | Description |
|---|---|---|---|
| invoice *required | path | string | Path parameter: Invoice |
| payment *required | path | string | Path parameter: Payment |
| store_id *required | body | integer | Selected accessible active store. |
| warehouse_id | body | integer | Optional selected accessible active warehouse. |
| recipient *required | body | string | Validated RFC email, maximum 254 characters; encrypted at rest, omitted from all returned data. |
| operation_key *required | body | string | Permanent UUID for document.invoice_payment.email. Identical retry returns only original delivery ID, never repeats invoice/payment/stock. |
{
"store_id": 1,
"warehouse_id": 1,
"recipient": "customer@example.invalid",
"operation_key": "00000000-0000-4000-8000-000000000001"
}
{
"success": true,
"data": {
"operation": {
"key": "00000000-0000-4000-8000-000000000001",
"replayed": false
},
"result_ids": {
"document_delivery_id": 1
},
"resource": {
"kind": "document_delivery",
"id": 1,
"document_kind": "invoice_payment",
"document_id": 1,
"status": "queued",
"queued_at": "2026-10-04T12: 00: 00+00: 00",
"attempted_at": null,
"accepted_at": null,
"error_code": null
}
}
}
{
"success": false,
"code": "validation_failed",
"message": "The given data was invalid.",
"errors": {
"store_id": [
"Select an accessible active store."
]
}
}
Authenticated private invoice/estimate/payment document. Requires invoices feature and live invoices.view or estimates.view as appropriate, existing view policy and complete parent/line/linked-record scope. No POS/payment permission prerequisite. Invoice payment IDs must belong to the exact nested invoice and original queued parent; deleted, missing or reparented children fail closed on request/replay/worker. Existing branding-aware templates and snapshots are reused. PDF response is original binary, private/no-store. Email is an independent permanent document operation, never a financial retry or state transition. Encrypted recipient is absent from result/operation/job payload; job contains only delivery ID. Enqueue failure stays pending; same-key retry safely queues that row once. Stale queued rows may be recovered; interrupted processing becomes uncertain, never automatic resend. accepted means provider acceptance, not inbox delivery; log/array transports fail. GET operations/{key} supplies current authorized delivery state. No client kind, URL, TTL, share key or provider authority.
| Parameter | In | Type | Description |
|---|---|---|---|
| invoice *required | path | string | Path parameter: Invoice |
| payment *required | path | string | Path parameter: Payment |
| store_id *required | query | integer | Selected accessible active store. |
| warehouse_id | query | integer | Optional selected accessible active warehouse. |
{
"store_id": 1,
"warehouse_id": 1
}
"%PDF-1.7 (synthetic binary example)"
{
"success": false,
"code": "validation_failed",
"message": "The given data was invalid.",
"errors": {
"store_id": [
"Select an accessible active store."
]
}
}
Authenticated private invoice/estimate/payment document. Requires invoices feature and live invoices.view or estimates.view as appropriate, existing view policy and complete parent/line/linked-record scope. No POS/payment permission prerequisite. Invoice payment IDs must belong to the exact nested invoice and original queued parent; deleted, missing or reparented children fail closed on request/replay/worker. Existing branding-aware templates and snapshots are reused. PDF response is original binary, private/no-store. Email is an independent permanent document operation, never a financial retry or state transition. Encrypted recipient is absent from result/operation/job payload; job contains only delivery ID. Enqueue failure stays pending; same-key retry safely queues that row once. Stale queued rows may be recovered; interrupted processing becomes uncertain, never automatic resend. accepted means provider acceptance, not inbox delivery; log/array transports fail. GET operations/{key} supplies current authorized delivery state. No client kind, URL, TTL, share key or provider authority.
| Parameter | In | Type | Description |
|---|---|---|---|
| invoice *required | path | string | Path parameter: Invoice |
| store_id *required | query | integer | Selected accessible active store. |
| warehouse_id | query | integer | Optional selected accessible active warehouse. |
{
"store_id": 1,
"warehouse_id": 1
}
"%PDF-1.7 (synthetic binary example)"
{
"success": false,
"code": "validation_failed",
"message": "The given data was invalid.",
"errors": {
"store_id": [
"Select an accessible active store."
]
}
}
Authenticated private sale document. Own completed POS sales require POS enabled, pos.access, sale/session ownership and full location access, not sales.view or sales.create. Linked Web Order sales require sales.view and the existing completed/fully paid sale plus paid linked-order eligibility. Every sale and order line must be accessible. No public URL or receipt number allocation. Private no-store delivery status only, current actor and exact selected location, latest first, maximum 10 linked deliveries. No recipient, body, operation key or provider reference. Pending/queued/processing/uncertain require review, never automatic resend.
| Parameter | In | Type | Description |
|---|---|---|---|
| sale *required | path | string | Path parameter: Sale |
| store_id *required | query | integer | Selected accessible active store. |
| warehouse_id | query | integer | Optional selected accessible active warehouse. |
{
"store_id": 1,
"warehouse_id": 1
}
{
"success": true,
"data": {
"items": [
{
"kind": "document_delivery",
"id": 1,
"document_kind": "sale",
"document_id": 1,
"status": "pending",
"queued_at": null,
"attempted_at": null,
"accepted_at": null,
"error_code": null
}
]
}
}
{
"success": false,
"code": "validation_failed",
"message": "The given data was invalid.",
"errors": {
"store_id": [
"Select an accessible active store."
]
}
}
Authenticated private sale document. Own completed POS sales require POS enabled, pos.access, sale/session ownership and full location access, not sales.view or sales.create. Linked Web Order sales require sales.view and the existing completed/fully paid sale plus paid linked-order eligibility. Every sale and order line must be accessible. No public URL or receipt number allocation. One encrypted-recipient delivery is linked to one permanent operation before after-commit enqueue. Enqueue failure stays pending and same-key retries/scheduler re-enqueue that row. Job payload contains delivery ID only and rechecks live access. accepted means provider acceptance, never inbox delivery. Log/array transports are failed; interrupted or ambiguous submissions become uncertain and require review, never automatic resend. GET operations/{key} returns current delivery state.
| Parameter | In | Type | Description |
|---|---|---|---|
| sale *required | path | string | Path parameter: Sale |
| store_id *required | body | integer | Selected accessible active store. |
| warehouse_id | body | integer | Optional selected accessible active warehouse. |
| recipient *required | body | string | Validated RFC email, maximum 254 characters; encrypted at rest and omitted from result. |
| operation_key *required | body | string | Permanent UUID for document.sale.email. Retry the same key and recipient; never repeat a sale/payment operation. |
{
"store_id": 1,
"warehouse_id": 1,
"recipient": "customer@example.invalid",
"operation_key": "00000000-0000-4000-8000-000000000001"
}
{
"success": true,
"data": {
"operation": {
"key": "00000000-0000-4000-8000-000000000001",
"replayed": false
},
"result_ids": {
"document_delivery_id": 1
},
"resource": {
"kind": "document_delivery",
"id": 1,
"document_kind": "sale",
"document_id": 1,
"status": "pending",
"queued_at": null,
"attempted_at": null,
"accepted_at": null,
"error_code": null
}
}
}
{
"success": false,
"code": "validation_failed",
"message": "The given data was invalid.",
"errors": {
"store_id": [
"Select an accessible active store."
]
}
}
Authenticated private sale document. Own completed POS sales require POS enabled, pos.access, sale/session ownership and full location access, not sales.view or sales.create. Linked Web Order sales require sales.view and the existing completed/fully paid sale plus paid linked-order eligibility. Every sale and order line must be accessible. No public URL or receipt number allocation. Private no-store binary PDF attachment.
| Parameter | In | Type | Description |
|---|---|---|---|
| sale *required | path | string | Path parameter: Sale |
| store_id *required | query | integer | Selected accessible active store. |
| warehouse_id | query | integer | Optional selected accessible active warehouse. |
{
"store_id": 1,
"warehouse_id": 1
}
"%PDF-1.7 (synthetic binary example)"
{
"success": false,
"code": "validation_failed",
"message": "The given data was invalid.",
"errors": {
"store_id": [
"Select an accessible active store."
]
}
}
Requires invoices feature enabled, estimates.view and the named action permission. Exact persisted native location is immutable. Root and all effective lines must be accessible before pagination, reads, mutations and replay; only NULL estimate lines inherit the authorized root. Shared invoice quotation owns pricing, explicit manual_unit_price permission/configuration, discounts, taxes and UOM. Estimate supports standard/exempt tax, not invoice WHT. New native quantities have up to three places; read-only historical quantities/base retain six places. Actual storage bounds apply: estimates quantity/base DECIMAL(10,6), invoice/sale primary DECIMAL(15,3), base DECIMAL(15,6), and each money column; overflow or unrepresentable conversion is a safe 422, never silent rounding. Drafts, duplication and invoice conversion are stock neutral. INV/EST numbers use global atomic monthly sequences, including deleted history and full suffixes beyond four digits. Current expiration behavior is retained. Snapshots are server-owned without historical backfill. PDF and email actions require fresh estimates capability, view policy and full scope; estimates have no public share. Native email submission does not change estimate state.
| Parameter | In | Type | Description |
|---|---|---|---|
| store_id *required | query | integer | Selected accessible active store. |
| warehouse_id | query | integer | Optional selected accessible active warehouse. |
| q | query | string | Literal number/customer search, maximum 100 characters. |
| page | query | integer | Page starting at one. |
| per_page | query | integer | Default 25; maximum 50. |
| status | query | string | Draft, sent, accepted, rejected, expired or converted. |
| date_from | query | string | Inclusive created date YYYY-MM-DD. |
| date_to | query | string | Inclusive created date YYYY-MM-DD. |
{
"store_id": 1,
"warehouse_id": 1,
"q": "example",
"page": 1,
"per_page": 1,
"status": "example",
"date_from": "example",
"date_to": "example"
}
{
"success": true,
"data": {
"items": [
{
"kind": "estimate",
"id": 1,
"estimate_number": "EST-202610-0001",
"revision": "opaque-example-revision",
"store_id": 1,
"warehouse_id": 1,
"status": "draft",
"is_expired": false,
"valid_until": "2026-11-05",
"created_at": "2026-10-04T12: 00: 00+00: 00",
"notes": null,
"terms": null,
"customer": {
"id": null,
"name": "Walk-in Customer"
},
"tax_treatment": "standard",
"items": [
{
"id": 1,
"item_id": 1,
"name": "Example Product",
"sku": "EXAMPLE-001",
"unit_id": null,
"warehouse_id": 1,
"quantity": "1.000000",
"base_quantity": "1.000000",
"unit_price": {
"amount": "100.00",
"currency": "GHS",
"formatted": "GH\u20b5100.00"
},
"discount": {
"amount": "0.00",
"currency": "GHS",
"formatted": "GH\u20b50.00"
},
"tax": {
"amount": "0.00",
"currency": "GHS",
"formatted": "GH\u20b50.00"
},
"total": {
"amount": "100.00",
"currency": "GHS",
"formatted": "GH\u20b5100.00"
},
"serial_number": null,
"price_source": "base",
"tax_breakdown": []
}
],
"totals": {
"subtotal": {
"amount": "100.00",
"currency": "GHS",
"formatted": "GH\u20b5100.00"
},
"tax": {
"amount": "0.00",
"currency": "GHS",
"formatted": "GH\u20b50.00"
},
"discount": {
"amount": "0.00",
"currency": "GHS",
"formatted": "GH\u20b50.00"
},
"total": {
"amount": "100.00",
"currency": "GHS",
"formatted": "GH\u20b5100.00"
}
},
"converted_to_invoice_id": null,
"converted_to_sale_id": null,
"actions": {
"edit": true,
"send": true,
"accept": true,
"reject": true,
"duplicate": true,
"convert_to_invoice": false,
"convert_to_sale": false,
"pdf": true,
"email": true,
"share": false
},
"converted_sale": null
}
]
},
"meta": {
"current_page": 1,
"last_page": 1,
"per_page": 25,
"total": 1
}
}
{
"success": false,
"code": "validation_failed",
"message": "The given data was invalid.",
"errors": {
"store_id": [
"Select an accessible active store."
]
}
}
Requires invoices feature enabled, estimates.view and the named action permission. Exact persisted native location is immutable. Root and all effective lines must be accessible before pagination, reads, mutations and replay; only NULL estimate lines inherit the authorized root. Shared invoice quotation owns pricing, explicit manual_unit_price permission/configuration, discounts, taxes and UOM. Estimate supports standard/exempt tax, not invoice WHT. New native quantities have up to three places; read-only historical quantities/base retain six places. Actual storage bounds apply: estimates quantity/base DECIMAL(10,6), invoice/sale primary DECIMAL(15,3), base DECIMAL(15,6), and each money column; overflow or unrepresentable conversion is a safe 422, never silent rounding. Drafts, duplication and invoice conversion are stock neutral. INV/EST numbers use global atomic monthly sequences, including deleted history and full suffixes beyond four digits. Current expiration behavior is retained. Snapshots are server-owned without historical backfill. PDF and email actions require fresh estimates capability, view policy and full scope; estimates have no public share. Native email submission does not change estimate state.
| Parameter | In | Type | Description |
|---|---|---|---|
| store_id *required | body | integer | Selected accessible active store. |
| warehouse_id | body | integer | Optional selected accessible active warehouse. |
| operation_key *required | body | string | Permanent actor-scoped UUID; committed replay precedes mutable checks. |
| quote_token *required | body | string | Estimate-purpose quotation token. |
| customer_id | body | integer | Global customer ID; live existence checked after committed replay. |
| notes | body | string | Invoice notes. |
| terms | body | string | Invoice terms. |
| items *required | body | array | Full line replacement. Required for creation; omit for metadata-only edit, retaining original lines and financial snapshots. |
| discount_type | body | string | Cart discount mode. |
| discount_value | body | string | Server converts percent intent to money, after line discounts and before subtracting from final total. |
| tax_treatment | body | string | Standard or exempt. |
| estimate_id | body | integer | Existing editable estimate for quote; must match PATCH route if supplied. |
| valid_until | body | string | Calendar date YYYY-MM-DD; defaults to thirty days for creation. |
{
"store_id": 1,
"warehouse_id": 1,
"items": [
{
"item_id": 1,
"quantity": "1.000"
}
],
"operation_key": "00000000-0000-4000-8000-000000000001",
"quote_token": "SYNTHETIC-ENCRYPTED-QUOTE"
}
{
"success": true,
"data": {
"operation": {
"key": "00000000-0000-4000-8000-000000000001",
"replayed": false
},
"result_ids": {
"estimate_id": 1
},
"resource": {
"kind": "estimate",
"id": 1,
"estimate_number": "EST-202610-0001",
"revision": "opaque-example-revision",
"store_id": 1,
"warehouse_id": 1,
"status": "draft",
"is_expired": false,
"valid_until": "2026-11-05",
"created_at": "2026-10-04T12: 00: 00+00: 00",
"notes": null,
"terms": null,
"customer": {
"id": null,
"name": "Walk-in Customer"
},
"tax_treatment": "standard",
"items": [
{
"id": 1,
"item_id": 1,
"name": "Example Product",
"sku": "EXAMPLE-001",
"unit_id": null,
"warehouse_id": 1,
"quantity": "1.000000",
"base_quantity": "1.000000",
"unit_price": {
"amount": "100.00",
"currency": "GHS",
"formatted": "GH\u20b5100.00"
},
"discount": {
"amount": "0.00",
"currency": "GHS",
"formatted": "GH\u20b50.00"
},
"tax": {
"amount": "0.00",
"currency": "GHS",
"formatted": "GH\u20b50.00"
},
"total": {
"amount": "100.00",
"currency": "GHS",
"formatted": "GH\u20b5100.00"
},
"serial_number": null,
"price_source": "base",
"tax_breakdown": []
}
],
"totals": {
"subtotal": {
"amount": "100.00",
"currency": "GHS",
"formatted": "GH\u20b5100.00"
},
"tax": {
"amount": "0.00",
"currency": "GHS",
"formatted": "GH\u20b50.00"
},
"discount": {
"amount": "0.00",
"currency": "GHS",
"formatted": "GH\u20b50.00"
},
"total": {
"amount": "100.00",
"currency": "GHS",
"formatted": "GH\u20b5100.00"
}
},
"converted_to_invoice_id": null,
"converted_to_sale_id": null,
"actions": {
"edit": true,
"send": true,
"accept": true,
"reject": true,
"duplicate": true,
"convert_to_invoice": false,
"convert_to_sale": false,
"pdf": true,
"email": true,
"share": false
},
"converted_sale": null
}
}
}
{
"success": false,
"code": "validation_failed",
"message": "The given data was invalid.",
"errors": {
"store_id": [
"Select an accessible active store."
]
}
}
Global minimal customer lookup for estimate workflow. Requires invoices feature, estimates.view and either estimates.create or estimates.edit. Does not require POS/invoice permissions or expose customer history; customers have no warehouse ownership. Optional store/warehouse query fields do not filter global customers. Default 25, maximum 50 per page. Private no-store.
| Parameter | In | Type | Description |
|---|---|---|---|
| q | query | string | Catalog text or barcode alias, maximum 100 characters. |
| page | query | integer | Page >= 1. |
| per_page | query | integer | Default 25; maximum 50. Invalid values return 422. |
{
"q": "example",
"page": 1,
"per_page": 1
}
{
"success": true,
"data": {
"items": [
{
"kind": "customer",
"id": 1,
"revision": "opaque-example-revision",
"name": "Example Customer",
"email": "customer@example.invalid",
"phone": null,
"address": null,
"city": null,
"state": null,
"postal_code": null,
"country": null
}
]
},
"meta": {
"current_page": 1,
"last_page": 1,
"per_page": 25,
"total": 1
}
}
{
"success": false,
"code": "validation_failed",
"message": "The given data was invalid.",
"errors": {
"store_id": [
"Select an accessible active store."
]
}
}
Requires invoices feature enabled, estimates.view and the named action permission. Exact persisted native location is immutable. Root and all effective lines must be accessible before pagination, reads, mutations and replay; only NULL estimate lines inherit the authorized root. Shared invoice quotation owns pricing, explicit manual_unit_price permission/configuration, discounts, taxes and UOM. Estimate supports standard/exempt tax, not invoice WHT. New native quantities have up to three places; read-only historical quantities/base retain six places. Actual storage bounds apply: estimates quantity/base DECIMAL(10,6), invoice/sale primary DECIMAL(15,3), base DECIMAL(15,6), and each money column; overflow or unrepresentable conversion is a safe 422, never silent rounding. Drafts, duplication and invoice conversion are stock neutral. INV/EST numbers use global atomic monthly sequences, including deleted history and full suffixes beyond four digits. Current expiration behavior is retained. Snapshots are server-owned without historical backfill. PDF and email actions require fresh estimates capability, view policy and full scope; estimates have no public share. Native email submission does not change estimate state. Active global catalog and active sale units. Scoped stock is not a reservation or completion guarantee.
| Parameter | In | Type | Description |
|---|---|---|---|
| store_id *required | query | integer | Selected accessible active store. |
| warehouse_id | query | integer | Optional selected accessible active warehouse. |
| q | query | string | Literal number/customer search, maximum 100 characters. |
| page | query | integer | Page starting at one. |
| per_page | query | integer | Default 25; maximum 50. |
| customer_id | query | integer | Customer pricing context. |
{
"store_id": 1,
"warehouse_id": 1,
"q": "example",
"page": 1,
"per_page": 1,
"customer_id": 1
}
{
"success": true,
"data": {
"items": [
{
"id": 1,
"name": "Example Product",
"sku": "EXAMPLE-001",
"price": {
"amount": "100.00",
"currency": "GHS",
"formatted": "GH\u20b5100.00"
},
"sale_units": [
{
"id": 1,
"name": "Piece",
"code": "pc",
"conversion_factor": "1.000000",
"price": {
"amount": "100.00",
"currency": "GHS",
"formatted": "GH\u20b5100.00"
}
}
],
"is_service": false,
"is_serialized": false,
"stock_quantity": "10.000000",
"manual_price_allowed": true
}
]
},
"meta": {
"current_page": 1,
"last_page": 1,
"per_page": 25,
"total": 1
}
}
{
"success": false,
"code": "validation_failed",
"message": "The given data was invalid.",
"errors": {
"store_id": [
"Select an accessible active store."
]
}
}
Exact active item ID lookup sharing the existing estimate catalog price, unit, configured-service and scoped-stock projection. Fresh respective capability/editor authority/current location; no POS prerequisite, reservation or financial write. Private no-store.
| Parameter | In | Type | Description |
|---|---|---|---|
| item *required | path | string | Path parameter: Item |
| store_id *required | body | integer | Selected accessible active store. |
| warehouse_id | body | integer | Optional selected accessible active warehouse. |
| customer_id | query | integer | Current customer pricing context. |
{
"store_id": 1,
"warehouse_id": 1,
"customer_id": 1
}
{
"success": true,
"data": {
"id": 1,
"name": "Example Product",
"sku": "EXAMPLE-001",
"price": {
"amount": "100.00",
"currency": "GHS",
"formatted": "GH\u20b5100.00"
},
"sale_units": [
{
"id": 1,
"name": "Piece",
"code": "pc",
"conversion_factor": "1.000000",
"price": {
"amount": "100.00",
"currency": "GHS",
"formatted": "GH\u20b5100.00"
}
}
],
"is_service": false,
"is_serialized": false,
"stock_quantity": "10.000000",
"manual_price_allowed": true
}
}
{
"success": false,
"code": "validation_failed",
"message": "The given data was invalid.",
"errors": {
"store_id": [
"Select an accessible active store."
]
}
}
Fresh estimates capability and create/edit authority at the valid selected scope. Reuses available serial and reservation filtering; no POS capability, reservation or stock mutation. Bounded SerialPage and private no-store. Workflow is selected by the registered route, not client input.
| Parameter | In | Type | Description |
|---|---|---|---|
| item *required | path | string | Path parameter: Item |
| store_id *required | query | integer | Selected accessible active store. |
| warehouse_id | query | integer | Optional selected accessible active warehouse. |
| q | query | string | Catalog text or barcode alias, maximum 100 characters. |
| category_id | query | integer | Category filter. |
| customer_id | query | integer | Global customer selected for effective server pricing. |
| page | query | integer | Page >= 1. |
| per_page | query | integer | Default 25; maximum 50. Invalid values return 422. |
{
"store_id": 1,
"warehouse_id": 1,
"q": "example",
"category_id": 1,
"customer_id": 1,
"page": 1,
"per_page": 1
}
{
"success": true,
"data": {
"serials": [
{
"id": 1,
"serial_number": "EXAMPLE-SERIAL"
}
]
},
"meta": {
"current_page": 1,
"last_page": 1,
"per_page": 25,
"total": 1
}
}
{
"success": false,
"code": "validation_failed",
"message": "The given data was invalid.",
"errors": {
"store_id": [
"Select an accessible active store."
]
}
}
Requires invoices feature enabled, estimates.view and the named action permission. Exact persisted native location is immutable. Root and all effective lines must be accessible before pagination, reads, mutations and replay; only NULL estimate lines inherit the authorized root. Shared invoice quotation owns pricing, explicit manual_unit_price permission/configuration, discounts, taxes and UOM. Estimate supports standard/exempt tax, not invoice WHT. New native quantities have up to three places; read-only historical quantities/base retain six places. Actual storage bounds apply: estimates quantity/base DECIMAL(10,6), invoice/sale primary DECIMAL(15,3), base DECIMAL(15,6), and each money column; overflow or unrepresentable conversion is a safe 422, never silent rounding. Drafts, duplication and invoice conversion are stock neutral. INV/EST numbers use global atomic monthly sequences, including deleted history and full suffixes beyond four digits. Current expiration behavior is retained. Snapshots are server-owned without historical backfill. PDF and email actions require fresh estimates capability, view policy and full scope; estimates have no public share. Native email submission does not change estimate state. Opaque five-minute token binds estimate purpose, actor, location, intent and current result; invoice/POS tokens are not interchangeable.
| Parameter | In | Type | Description |
|---|---|---|---|
| store_id *required | body | integer | Selected accessible active store. |
| warehouse_id | body | integer | Optional selected accessible active warehouse. |
| customer_id | body | integer | Global customer ID; live existence checked after committed replay. |
| notes | body | string | Invoice notes. |
| terms | body | string | Invoice terms. |
| items | body | array | Full line replacement. Required for creation; omit for metadata-only edit, retaining original lines and financial snapshots. |
| discount_type | body | string | Cart discount mode. |
| discount_value | body | string | Server converts percent intent to money, after line discounts and before subtracting from final total. |
| tax_treatment | body | string | Standard or exempt. |
| estimate_id | body | integer | Existing editable estimate for quote; must match PATCH route if supplied. |
| valid_until | body | string | Calendar date YYYY-MM-DD; defaults to thirty days for creation. |
{
"store_id": 1,
"warehouse_id": 1,
"items": [
{
"item_id": 1,
"quantity": "1.000"
}
]
}
{
"success": true,
"data": {
"lines": [
{
"item_id": 1,
"name": "Example Product",
"sku": "EXAMPLE-001",
"unit_id": null,
"quantity": "1.000000",
"base_quantity": "1.000000",
"unit_price": {
"amount": "100.00",
"currency": "GHS",
"formatted": "GH\u20b5100.00"
},
"discount": {
"amount": "0.00",
"currency": "GHS",
"formatted": "GH\u20b50.00"
},
"tax": {
"amount": "0.00",
"currency": "GHS",
"formatted": "GH\u20b50.00"
},
"total": {
"amount": "100.00",
"currency": "GHS",
"formatted": "GH\u20b5100.00"
},
"serial_number": null,
"price_source": "base",
"tax_breakdown": []
}
],
"totals": {
"subtotal": {
"amount": "100.00",
"currency": "GHS",
"formatted": "GH\u20b5100.00"
},
"tax": {
"amount": "0.00",
"currency": "GHS",
"formatted": "GH\u20b50.00"
},
"discount": {
"amount": "0.00",
"currency": "GHS",
"formatted": "GH\u20b50.00"
},
"total": {
"amount": "100.00",
"currency": "GHS",
"formatted": "GH\u20b5100.00"
},
"withholding_tax": {
"amount": "0.00",
"currency": "GHS",
"formatted": "GH\u20b50.00"
},
"amount_paid": {
"amount": "0.00",
"currency": "GHS",
"formatted": "GH\u20b50.00"
},
"amount_due": {
"amount": "100.00",
"currency": "GHS",
"formatted": "GH\u20b5100.00"
}
},
"tax_treatment": "standard",
"manual_price_allowed": true,
"revision": null,
"quote_token": "SYNTHETIC-ENCRYPTED-QUOTE",
"expires_at": "2026-10-05T12: 05: 00+00: 00"
}
}
{
"success": false,
"code": "validation_failed",
"message": "The given data was invalid.",
"errors": {
"store_id": [
"Select an accessible active store."
]
}
}
Requires invoices feature enabled, estimates.view and the named action permission. Exact persisted native location is immutable. Root and all effective lines must be accessible before pagination, reads, mutations and replay; only NULL estimate lines inherit the authorized root. Shared invoice quotation owns pricing, explicit manual_unit_price permission/configuration, discounts, taxes and UOM. Estimate supports standard/exempt tax, not invoice WHT. New native quantities have up to three places; read-only historical quantities/base retain six places. Actual storage bounds apply: estimates quantity/base DECIMAL(10,6), invoice/sale primary DECIMAL(15,3), base DECIMAL(15,6), and each money column; overflow or unrepresentable conversion is a safe 422, never silent rounding. Drafts, duplication and invoice conversion are stock neutral. INV/EST numbers use global atomic monthly sequences, including deleted history and full suffixes beyond four digits. Current expiration behavior is retained. Snapshots are server-owned without historical backfill. PDF and email actions require fresh estimates capability, view policy and full scope; estimates have no public share. Native email submission does not change estimate state.
| Parameter | In | Type | Description |
|---|---|---|---|
| estimate *required | path | string | Path parameter: Estimate |
| store_id *required | query | integer | Selected accessible active store. |
| warehouse_id | query | integer | Optional selected accessible active warehouse. |
{
"store_id": 1,
"warehouse_id": 1
}
{
"success": true,
"data": {
"kind": "estimate",
"id": 1,
"estimate_number": "EST-202610-0001",
"revision": "opaque-example-revision",
"store_id": 1,
"warehouse_id": 1,
"status": "draft",
"is_expired": false,
"valid_until": "2026-11-05",
"created_at": "2026-10-04T12: 00: 00+00: 00",
"notes": null,
"terms": null,
"customer": {
"id": null,
"name": "Walk-in Customer"
},
"tax_treatment": "standard",
"items": [
{
"id": 1,
"item_id": 1,
"name": "Example Product",
"sku": "EXAMPLE-001",
"unit_id": null,
"warehouse_id": 1,
"quantity": "1.000000",
"base_quantity": "1.000000",
"unit_price": {
"amount": "100.00",
"currency": "GHS",
"formatted": "GH\u20b5100.00"
},
"discount": {
"amount": "0.00",
"currency": "GHS",
"formatted": "GH\u20b50.00"
},
"tax": {
"amount": "0.00",
"currency": "GHS",
"formatted": "GH\u20b50.00"
},
"total": {
"amount": "100.00",
"currency": "GHS",
"formatted": "GH\u20b5100.00"
},
"serial_number": null,
"price_source": "base",
"tax_breakdown": []
}
],
"totals": {
"subtotal": {
"amount": "100.00",
"currency": "GHS",
"formatted": "GH\u20b5100.00"
},
"tax": {
"amount": "0.00",
"currency": "GHS",
"formatted": "GH\u20b50.00"
},
"discount": {
"amount": "0.00",
"currency": "GHS",
"formatted": "GH\u20b50.00"
},
"total": {
"amount": "100.00",
"currency": "GHS",
"formatted": "GH\u20b5100.00"
}
},
"converted_to_invoice_id": null,
"converted_to_sale_id": null,
"actions": {
"edit": true,
"send": true,
"accept": true,
"reject": true,
"duplicate": true,
"convert_to_invoice": false,
"convert_to_sale": false,
"pdf": true,
"email": true,
"share": false
},
"converted_sale": null
}
}
{
"success": false,
"code": "validation_failed",
"message": "The given data was invalid.",
"errors": {
"store_id": [
"Select an accessible active store."
]
}
}
Requires invoices feature enabled, estimates.view and the named action permission. Exact persisted native location is immutable. Root and all effective lines must be accessible before pagination, reads, mutations and replay; only NULL estimate lines inherit the authorized root. Shared invoice quotation owns pricing, explicit manual_unit_price permission/configuration, discounts, taxes and UOM. Estimate supports standard/exempt tax, not invoice WHT. New native quantities have up to three places; read-only historical quantities/base retain six places. Actual storage bounds apply: estimates quantity/base DECIMAL(10,6), invoice/sale primary DECIMAL(15,3), base DECIMAL(15,6), and each money column; overflow or unrepresentable conversion is a safe 422, never silent rounding. Drafts, duplication and invoice conversion are stock neutral. INV/EST numbers use global atomic monthly sequences, including deleted history and full suffixes beyond four digits. Current expiration behavior is retained. Snapshots are server-owned without historical backfill. PDF and email actions require fresh estimates capability, view policy and full scope; estimates have no public share. Native email submission does not change estimate state.
| Parameter | In | Type | Description |
|---|---|---|---|
| estimate *required | path | string | Path parameter: Estimate |
| store_id *required | body | integer | Selected accessible active store. |
| warehouse_id | body | integer | Optional selected accessible active warehouse. |
| operation_key *required | body | string | Permanent actor-scoped UUID; committed replay precedes mutable checks. |
| quote_token *required | body | string | Estimate-purpose quotation token. |
| revision *required | body | string | Current estimate revision. |
| customer_id | body | integer | Global customer ID; live existence checked after committed replay. |
| notes | body | string | Invoice notes. |
| terms | body | string | Invoice terms. |
| items | body | array | Full line replacement. Required for creation; omit for metadata-only edit, retaining original lines and financial snapshots. |
| discount_type | body | string | Cart discount mode. |
| discount_value | body | string | Server converts percent intent to money, after line discounts and before subtracting from final total. |
| tax_treatment | body | string | Standard or exempt. |
| estimate_id | body | integer | Existing editable estimate for quote; must match PATCH route if supplied. |
| valid_until | body | string | Calendar date YYYY-MM-DD; defaults to thirty days for creation. |
{
"store_id": 1,
"warehouse_id": 1,
"items": [
{
"item_id": 1,
"quantity": "1.000"
}
],
"operation_key": "00000000-0000-4000-8000-000000000001",
"quote_token": "SYNTHETIC-ENCRYPTED-QUOTE",
"estimate_id": 1,
"revision": "opaque-example-revision"
}
{
"success": true,
"data": {
"operation": {
"key": "00000000-0000-4000-8000-000000000001",
"replayed": false
},
"result_ids": {
"estimate_id": 1
},
"resource": {
"kind": "estimate",
"id": 1,
"estimate_number": "EST-202610-0001",
"revision": "opaque-example-revision",
"store_id": 1,
"warehouse_id": 1,
"status": "draft",
"is_expired": false,
"valid_until": "2026-11-05",
"created_at": "2026-10-04T12: 00: 00+00: 00",
"notes": null,
"terms": null,
"customer": {
"id": null,
"name": "Walk-in Customer"
},
"tax_treatment": "standard",
"items": [
{
"id": 1,
"item_id": 1,
"name": "Example Product",
"sku": "EXAMPLE-001",
"unit_id": null,
"warehouse_id": 1,
"quantity": "1.000000",
"base_quantity": "1.000000",
"unit_price": {
"amount": "100.00",
"currency": "GHS",
"formatted": "GH\u20b5100.00"
},
"discount": {
"amount": "0.00",
"currency": "GHS",
"formatted": "GH\u20b50.00"
},
"tax": {
"amount": "0.00",
"currency": "GHS",
"formatted": "GH\u20b50.00"
},
"total": {
"amount": "100.00",
"currency": "GHS",
"formatted": "GH\u20b5100.00"
},
"serial_number": null,
"price_source": "base",
"tax_breakdown": []
}
],
"totals": {
"subtotal": {
"amount": "100.00",
"currency": "GHS",
"formatted": "GH\u20b5100.00"
},
"tax": {
"amount": "0.00",
"currency": "GHS",
"formatted": "GH\u20b50.00"
},
"discount": {
"amount": "0.00",
"currency": "GHS",
"formatted": "GH\u20b50.00"
},
"total": {
"amount": "100.00",
"currency": "GHS",
"formatted": "GH\u20b5100.00"
}
},
"converted_to_invoice_id": null,
"converted_to_sale_id": null,
"actions": {
"edit": true,
"send": true,
"accept": true,
"reject": true,
"duplicate": true,
"convert_to_invoice": false,
"convert_to_sale": false,
"pdf": true,
"email": true,
"share": false
},
"converted_sale": null
}
}
}
{
"success": false,
"code": "validation_failed",
"message": "The given data was invalid.",
"errors": {
"store_id": [
"Select an accessible active store."
]
}
}
Requires invoices feature enabled, estimates.view and the named action permission. Exact persisted native location is immutable. Root and all effective lines must be accessible before pagination, reads, mutations and replay; only NULL estimate lines inherit the authorized root. Shared invoice quotation owns pricing, explicit manual_unit_price permission/configuration, discounts, taxes and UOM. Estimate supports standard/exempt tax, not invoice WHT. New native quantities have up to three places; read-only historical quantities/base retain six places. Actual storage bounds apply: estimates quantity/base DECIMAL(10,6), invoice/sale primary DECIMAL(15,3), base DECIMAL(15,6), and each money column; overflow or unrepresentable conversion is a safe 422, never silent rounding. Drafts, duplication and invoice conversion are stock neutral. INV/EST numbers use global atomic monthly sequences, including deleted history and full suffixes beyond four digits. Current expiration behavior is retained. Snapshots are server-owned without historical backfill. PDF and email actions require fresh estimates capability, view policy and full scope; estimates have no public share. Native email submission does not change estimate state. Conversion needs Sent/Accepted and unexpired eligibility. Invoice conversion creates Draft with no stock effect. Sale conversion additionally needs live POS/sales.create, an open owned session, explicit matching item/unit/quantity/customer cart (serial selection may complete unfinished lines), a fresh POS quote and confirmed configured manual tender. One outer permanent operation owns checkout; no nested runner or second pricing algorithm. Same-key replay returns original root/owned-child IDs and current authorized resources, requiring both destination capability/permission and complete child scope. Duplication retains immutable source linkage and leaves original state unchanged.
| Parameter | In | Type | Description |
|---|---|---|---|
| estimate *required | path | string | Path parameter: Estimate |
| store_id *required | body | integer | Selected accessible active store. |
| warehouse_id | body | integer | Optional selected accessible active warehouse. |
| operation_key *required | body | string | Permanent actor-scoped UUID for this exact estimate action. |
| revision *required | body | string | Current estimate revision; replay precedes state and revision checks. |
{
"store_id": 1,
"warehouse_id": 1,
"operation_key": "00000000-0000-4000-8000-000000000001",
"revision": "opaque-example-revision"
}
{
"success": true,
"data": {
"operation": {
"key": "00000000-0000-4000-8000-000000000001",
"replayed": false
},
"result_ids": {
"estimate_id": 1
},
"resource": {
"kind": "estimate",
"id": 1,
"estimate_number": "EST-202610-0001",
"revision": "opaque-example-revision",
"store_id": 1,
"warehouse_id": 1,
"status": "accepted",
"is_expired": false,
"valid_until": "2026-11-05",
"created_at": "2026-10-04T12: 00: 00+00: 00",
"notes": null,
"terms": null,
"customer": {
"id": null,
"name": "Walk-in Customer"
},
"tax_treatment": "standard",
"items": [
{
"id": 1,
"item_id": 1,
"name": "Example Product",
"sku": "EXAMPLE-001",
"unit_id": null,
"warehouse_id": 1,
"quantity": "1.000000",
"base_quantity": "1.000000",
"unit_price": {
"amount": "100.00",
"currency": "GHS",
"formatted": "GH\u20b5100.00"
},
"discount": {
"amount": "0.00",
"currency": "GHS",
"formatted": "GH\u20b50.00"
},
"tax": {
"amount": "0.00",
"currency": "GHS",
"formatted": "GH\u20b50.00"
},
"total": {
"amount": "100.00",
"currency": "GHS",
"formatted": "GH\u20b5100.00"
},
"serial_number": null,
"price_source": "base",
"tax_breakdown": []
}
],
"totals": {
"subtotal": {
"amount": "100.00",
"currency": "GHS",
"formatted": "GH\u20b5100.00"
},
"tax": {
"amount": "0.00",
"currency": "GHS",
"formatted": "GH\u20b50.00"
},
"discount": {
"amount": "0.00",
"currency": "GHS",
"formatted": "GH\u20b50.00"
},
"total": {
"amount": "100.00",
"currency": "GHS",
"formatted": "GH\u20b5100.00"
}
},
"converted_to_invoice_id": null,
"converted_to_sale_id": null,
"actions": {
"edit": false,
"send": false,
"accept": false,
"reject": false,
"duplicate": true,
"convert_to_invoice": true,
"convert_to_sale": true,
"pdf": true,
"email": true,
"share": false
},
"converted_sale": null
}
}
}
{
"success": false,
"code": "validation_failed",
"message": "The given data was invalid.",
"errors": {
"store_id": [
"Select an accessible active store."
]
}
}
Requires invoices feature enabled, estimates.view and the named action permission. Exact persisted native location is immutable. Root and all effective lines must be accessible before pagination, reads, mutations and replay; only NULL estimate lines inherit the authorized root. Shared invoice quotation owns pricing, explicit manual_unit_price permission/configuration, discounts, taxes and UOM. Estimate supports standard/exempt tax, not invoice WHT. New native quantities have up to three places; read-only historical quantities/base retain six places. Actual storage bounds apply: estimates quantity/base DECIMAL(10,6), invoice/sale primary DECIMAL(15,3), base DECIMAL(15,6), and each money column; overflow or unrepresentable conversion is a safe 422, never silent rounding. Drafts, duplication and invoice conversion are stock neutral. INV/EST numbers use global atomic monthly sequences, including deleted history and full suffixes beyond four digits. Current expiration behavior is retained. Snapshots are server-owned without historical backfill. PDF and email actions require fresh estimates capability, view policy and full scope; estimates have no public share. Native email submission does not change estimate state. Conversion needs Sent/Accepted and unexpired eligibility. Invoice conversion creates Draft with no stock effect. Sale conversion additionally needs live POS/sales.create, an open owned session, explicit matching item/unit/quantity/customer cart (serial selection may complete unfinished lines), a fresh POS quote and confirmed configured manual tender. One outer permanent operation owns checkout; no nested runner or second pricing algorithm. Same-key replay returns original root/owned-child IDs and current authorized resources, requiring both destination capability/permission and complete child scope. Duplication retains immutable source linkage and leaves original state unchanged.
| Parameter | In | Type | Description |
|---|---|---|---|
| estimate *required | path | string | Path parameter: Estimate |
| store_id *required | body | integer | Selected accessible active store. |
| warehouse_id | body | integer | Optional selected accessible active warehouse. |
| operation_key *required | body | string | Permanent actor-scoped UUID for this exact estimate action. |
| revision *required | body | string | Current estimate revision; replay precedes state and revision checks. |
{
"store_id": 1,
"warehouse_id": 1,
"operation_key": "00000000-0000-4000-8000-000000000001",
"revision": "opaque-example-revision"
}
{
"success": true,
"data": {
"operation": {
"key": "00000000-0000-4000-8000-000000000001",
"replayed": false
},
"result_ids": {
"estimate_id": 1,
"invoice_id": 1
},
"resource": {
"kind": "estimate_conversion",
"estimate": {
"kind": "estimate",
"id": 1,
"estimate_number": "EST-202610-0001",
"revision": "opaque-example-revision",
"store_id": 1,
"warehouse_id": 1,
"status": "converted",
"is_expired": false,
"valid_until": "2026-11-05",
"created_at": "2026-10-04T12: 00: 00+00: 00",
"notes": null,
"terms": null,
"customer": {
"id": null,
"name": "Walk-in Customer"
},
"tax_treatment": "standard",
"items": [
{
"id": 1,
"item_id": 1,
"name": "Example Product",
"sku": "EXAMPLE-001",
"unit_id": null,
"warehouse_id": 1,
"quantity": "1.000000",
"base_quantity": "1.000000",
"unit_price": {
"amount": "100.00",
"currency": "GHS",
"formatted": "GH\u20b5100.00"
},
"discount": {
"amount": "0.00",
"currency": "GHS",
"formatted": "GH\u20b50.00"
},
"tax": {
"amount": "0.00",
"currency": "GHS",
"formatted": "GH\u20b50.00"
},
"total": {
"amount": "100.00",
"currency": "GHS",
"formatted": "GH\u20b5100.00"
},
"serial_number": null,
"price_source": "base",
"tax_breakdown": []
}
],
"totals": {
"subtotal": {
"amount": "100.00",
"currency": "GHS",
"formatted": "GH\u20b5100.00"
},
"tax": {
"amount": "0.00",
"currency": "GHS",
"formatted": "GH\u20b50.00"
},
"discount": {
"amount": "0.00",
"currency": "GHS",
"formatted": "GH\u20b50.00"
},
"total": {
"amount": "100.00",
"currency": "GHS",
"formatted": "GH\u20b5100.00"
}
},
"converted_to_invoice_id": 1,
"converted_to_sale_id": null,
"actions": {
"edit": false,
"send": false,
"accept": false,
"reject": false,
"duplicate": true,
"convert_to_invoice": false,
"convert_to_sale": false,
"pdf": true,
"email": true,
"share": false
},
"converted_sale": null
},
"result": {
"kind": "invoice",
"id": 1,
"invoice_number": "INV-EXAMPLE-001",
"revision": "opaque-example-revision",
"store_id": 1,
"warehouse_id": 1,
"status": "draft",
"customer": {
"id": null,
"name": null
},
"due_date": null,
"created_at": "2026-10-04T12: 00: 00+00: 00",
"notes": null,
"terms": null,
"tax_treatment": "standard",
"items": [
{
"id": 1,
"item_id": 1,
"name": "Example Product",
"sku": "EXAMPLE-001",
"unit_id": null,
"warehouse_id": 1,
"quantity": "1.000",
"base_quantity": "1.000000",
"unit_price": {
"amount": "100.00",
"currency": "GHS",
"formatted": "GH\u20b5100.00"
},
"discount": {
"amount": "0.00",
"currency": "GHS",
"formatted": "GH\u20b50.00"
},
"tax": {
"amount": "0.00",
"currency": "GHS",
"formatted": "GH\u20b50.00"
},
"total": {
"amount": "100.00",
"currency": "GHS",
"formatted": "GH\u20b5100.00"
},
"serial_number": null,
"price_source": "base",
"tax_breakdown": []
}
],
"totals": {
"subtotal": {
"amount": "100.00",
"currency": "GHS",
"formatted": "GH\u20b5100.00"
},
"tax": {
"amount": "0.00",
"currency": "GHS",
"formatted": "GH\u20b50.00"
},
"discount": {
"amount": "0.00",
"currency": "GHS",
"formatted": "GH\u20b50.00"
},
"total": {
"amount": "100.00",
"currency": "GHS",
"formatted": "GH\u20b5100.00"
},
"withholding_tax": {
"amount": "0.00",
"currency": "GHS",
"formatted": "GH\u20b50.00"
},
"amount_paid": {
"amount": "0.00",
"currency": "GHS",
"formatted": "GH\u20b50.00"
},
"amount_due": {
"amount": "100.00",
"currency": "GHS",
"formatted": "GH\u20b5100.00"
}
},
"withholding_tax": {
"type_id": null,
"rate": null,
"base": null,
"reference": null,
"locked": false
},
"total_credited": {
"amount": "0.00",
"currency": "GHS",
"formatted": "GH\u20b50.00"
},
"total_refunded": {
"amount": "0.00",
"currency": "GHS",
"formatted": "GH\u20b50.00"
},
"net_collected": {
"amount": "0.00",
"currency": "GHS",
"formatted": "GH\u20b50.00"
},
"payments": [],
"payment_methods": [
{
"id": 1,
"name": "Cash",
"code": "cash",
"type": "cash",
"requires_reference": false
}
],
"actions": {
"edit": true,
"finalize": false,
"payment": false,
"cancel": false,
"pdf": true,
"email": true,
"share": true,
"create_credit": false,
"credit_history": true
},
"source_workflow": "invoice",
"source_sale": null,
"unavailable_reason": null
}
}
}
}
{
"success": false,
"code": "validation_failed",
"message": "The given data was invalid.",
"errors": {
"store_id": [
"Select an accessible active store."
]
}
}
Requires invoices feature enabled, estimates.view and the named action permission. Exact persisted native location is immutable. Root and all effective lines must be accessible before pagination, reads, mutations and replay; only NULL estimate lines inherit the authorized root. Shared invoice quotation owns pricing, explicit manual_unit_price permission/configuration, discounts, taxes and UOM. Estimate supports standard/exempt tax, not invoice WHT. New native quantities have up to three places; read-only historical quantities/base retain six places. Actual storage bounds apply: estimates quantity/base DECIMAL(10,6), invoice/sale primary DECIMAL(15,3), base DECIMAL(15,6), and each money column; overflow or unrepresentable conversion is a safe 422, never silent rounding. Drafts, duplication and invoice conversion are stock neutral. INV/EST numbers use global atomic monthly sequences, including deleted history and full suffixes beyond four digits. Current expiration behavior is retained. Snapshots are server-owned without historical backfill. PDF and email actions require fresh estimates capability, view policy and full scope; estimates have no public share. Native email submission does not change estimate state. Conversion needs Sent/Accepted and unexpired eligibility. Invoice conversion creates Draft with no stock effect. Sale conversion additionally needs live POS/sales.create, an open owned session, explicit matching item/unit/quantity/customer cart (serial selection may complete unfinished lines), a fresh POS quote and confirmed configured manual tender. One outer permanent operation owns checkout; no nested runner or second pricing algorithm. Same-key replay returns original root/owned-child IDs and current authorized resources, requiring both destination capability/permission and complete child scope. Duplication retains immutable source linkage and leaves original state unchanged.
| Parameter | In | Type | Description |
|---|---|---|---|
| estimate *required | path | string | Path parameter: Estimate |
| store_id *required | body | integer | Selected accessible active store. |
| warehouse_id | body | integer | Optional selected accessible active warehouse. |
| operation_key *required | body | string | Permanent actor-scoped UUID for this exact estimate action. |
| revision *required | body | string | Current estimate revision; replay precedes state and revision checks. |
| items *required | body | array | One to 100 lines; quantities up to three decimal places. A serialized line has quantity one and a unique serial. |
| customer_id | body | integer | Existing global customer for server pricing. |
| tax_exempt | body | boolean | Explicit tax exemption. |
| cart_discount_type | body | string | percent or amount. |
| cart_discount_value | body | string | Nonnegative decimal string, up to two places. |
| cart_discount_reason | body | string | Maximum 500 characters. |
| notes | body | string | Maximum 500 characters. |
| approval_token | body | string | Five-minute encrypted manager approval bound to actor, selected location and exact cart. |
| quote_token *required | body | string | Opaque POS quote token. |
| payment_confirmed *required | body | boolean | Must confirm actual receipt of external money. |
| payments *required | body | array | Applied amounts sum exactly to amount due; cash tender may exceed applied amount, with server change. Noncash requires exact tender and reference. |
{
"store_id": 1,
"warehouse_id": 1,
"items": [
{
"id": 1,
"qty": "1.000"
}
],
"operation_key": "00000000-0000-4000-8000-000000000001",
"quote_token": "SYNTHETIC-ENCRYPTED-QUOTE",
"payment_confirmed": true,
"payments": [
{
"payment_method_id": 1,
"amount": "12.50",
"tendered": "20.00"
}
],
"revision": "opaque-example-revision"
}
{
"success": true,
"data": {
"operation": {
"key": "00000000-0000-4000-8000-000000000001",
"replayed": false
},
"result_ids": {
"estimate_id": 1,
"sale_id": 1
},
"resource": {
"kind": "estimate_conversion",
"estimate": {
"kind": "estimate",
"id": 1,
"estimate_number": "EST-202610-0001",
"revision": "opaque-example-revision",
"store_id": 1,
"warehouse_id": 1,
"status": "converted",
"is_expired": false,
"valid_until": "2026-11-05",
"created_at": "2026-10-04T12: 00: 00+00: 00",
"notes": null,
"terms": null,
"customer": {
"id": null,
"name": "Walk-in Customer"
},
"tax_treatment": "standard",
"items": [
{
"id": 1,
"item_id": 1,
"name": "Example Product",
"sku": "EXAMPLE-001",
"unit_id": null,
"warehouse_id": 1,
"quantity": "1.000000",
"base_quantity": "1.000000",
"unit_price": {
"amount": "100.00",
"currency": "GHS",
"formatted": "GH\u20b5100.00"
},
"discount": {
"amount": "0.00",
"currency": "GHS",
"formatted": "GH\u20b50.00"
},
"tax": {
"amount": "0.00",
"currency": "GHS",
"formatted": "GH\u20b50.00"
},
"total": {
"amount": "100.00",
"currency": "GHS",
"formatted": "GH\u20b5100.00"
},
"serial_number": null,
"price_source": "base",
"tax_breakdown": []
}
],
"totals": {
"subtotal": {
"amount": "100.00",
"currency": "GHS",
"formatted": "GH\u20b5100.00"
},
"tax": {
"amount": "0.00",
"currency": "GHS",
"formatted": "GH\u20b50.00"
},
"discount": {
"amount": "0.00",
"currency": "GHS",
"formatted": "GH\u20b50.00"
},
"total": {
"amount": "100.00",
"currency": "GHS",
"formatted": "GH\u20b5100.00"
}
},
"converted_to_invoice_id": null,
"converted_to_sale_id": 1,
"actions": {
"edit": false,
"send": false,
"accept": false,
"reject": false,
"duplicate": true,
"convert_to_invoice": false,
"convert_to_sale": false,
"pdf": true,
"email": true,
"share": false
},
"converted_sale": null
},
"result": {
"kind": "sale",
"id": 1,
"revision": "opaque-example-revision",
"receipt_number": "RCP-20261005-0001",
"store_id": 1,
"warehouse_id": 1,
"cashier_session_id": 1,
"total": {
"amount": "12.50",
"currency": "GHS",
"formatted": "GH\u20b512.50"
},
"paid": {
"amount": "12.50",
"currency": "GHS",
"formatted": "GH\u20b512.50"
},
"change": {
"amount": "7.50",
"currency": "GHS",
"formatted": "GH\u20b57.50"
},
"items": [
{
"item_id": 1,
"quantity": "1.000",
"serial_number": null,
"price": {
"amount": "12.50",
"currency": "GHS",
"formatted": "GH\u20b512.50"
}
}
],
"payments": [
{
"payment_method_id": 1,
"code": "cash",
"amount": {
"amount": "12.50",
"currency": "GHS",
"formatted": "GH\u20b512.50"
},
"tendered": {
"amount": "20.00",
"currency": "GHS",
"formatted": "GH\u20b520.00"
},
"change": {
"amount": "7.50",
"currency": "GHS",
"formatted": "GH\u20b57.50"
}
}
],
"allowed_actions": {
"download_pdf": true,
"share": true,
"print": true,
"email": true
}
}
}
}
}
{
"success": false,
"code": "validation_failed",
"message": "The given data was invalid.",
"errors": {
"store_id": [
"Select an accessible active store."
]
}
}
Current actor and exact selected location, fresh capability and whole parent scope. Payment receipt additionally requires the explicit Invoice parent and retained Payment ownership. Maximum 10 linked statuses; blocking pending/queued/processing/uncertain prioritized before terminal statuses so old uncertain sends remain visible. No recipient, body or provider reference. Read-only and private no-store; never resend while blocking or unknown.
| Parameter | In | Type | Description |
|---|---|---|---|
| estimate *required | path | string | Path parameter: Estimate |
| store_id *required | query | integer | Selected accessible active store. |
| warehouse_id | query | integer | Optional selected accessible active warehouse. |
{
"store_id": 1,
"warehouse_id": 1
}
{
"success": true,
"data": {
"items": [
{
"kind": "document_delivery",
"id": 1,
"document_kind": "estimate",
"document_id": 1,
"status": "pending",
"queued_at": null,
"attempted_at": null,
"accepted_at": null,
"error_code": null
}
]
}
}
{
"success": false,
"code": "validation_failed",
"message": "The given data was invalid.",
"errors": {
"store_id": [
"Select an accessible active store."
]
}
}
Requires invoices feature enabled, estimates.view and the named action permission. Exact persisted native location is immutable. Root and all effective lines must be accessible before pagination, reads, mutations and replay; only NULL estimate lines inherit the authorized root. Shared invoice quotation owns pricing, explicit manual_unit_price permission/configuration, discounts, taxes and UOM. Estimate supports standard/exempt tax, not invoice WHT. New native quantities have up to three places; read-only historical quantities/base retain six places. Actual storage bounds apply: estimates quantity/base DECIMAL(10,6), invoice/sale primary DECIMAL(15,3), base DECIMAL(15,6), and each money column; overflow or unrepresentable conversion is a safe 422, never silent rounding. Drafts, duplication and invoice conversion are stock neutral. INV/EST numbers use global atomic monthly sequences, including deleted history and full suffixes beyond four digits. Current expiration behavior is retained. Snapshots are server-owned without historical backfill. PDF and email actions require fresh estimates capability, view policy and full scope; estimates have no public share. Native email submission does not change estimate state. Conversion needs Sent/Accepted and unexpired eligibility. Invoice conversion creates Draft with no stock effect. Sale conversion additionally needs live POS/sales.create, an open owned session, explicit matching item/unit/quantity/customer cart (serial selection may complete unfinished lines), a fresh POS quote and confirmed configured manual tender. One outer permanent operation owns checkout; no nested runner or second pricing algorithm. Same-key replay returns original root/owned-child IDs and current authorized resources, requiring both destination capability/permission and complete child scope. Duplication retains immutable source linkage and leaves original state unchanged.
| Parameter | In | Type | Description |
|---|---|---|---|
| estimate *required | path | string | Path parameter: Estimate |
| store_id *required | body | integer | Selected accessible active store. |
| warehouse_id | body | integer | Optional selected accessible active warehouse. |
| operation_key *required | body | string | Permanent actor-scoped UUID for this exact estimate action. |
| revision *required | body | string | Current estimate revision; replay precedes state and revision checks. |
{
"store_id": 1,
"warehouse_id": 1,
"operation_key": "00000000-0000-4000-8000-000000000001",
"revision": "opaque-example-revision"
}
{
"success": true,
"data": {
"operation": {
"key": "00000000-0000-4000-8000-000000000001",
"replayed": false
},
"result_ids": {
"estimate_id": 1,
"duplicate_estimate_id": 2
},
"resource": {
"kind": "estimate_duplicate",
"estimate": {
"kind": "estimate",
"id": 1,
"estimate_number": "EST-202610-0001",
"revision": "opaque-example-revision",
"store_id": 1,
"warehouse_id": 1,
"status": "draft",
"is_expired": false,
"valid_until": "2026-11-05",
"created_at": "2026-10-04T12: 00: 00+00: 00",
"notes": null,
"terms": null,
"customer": {
"id": null,
"name": "Walk-in Customer"
},
"tax_treatment": "standard",
"items": [
{
"id": 1,
"item_id": 1,
"name": "Example Product",
"sku": "EXAMPLE-001",
"unit_id": null,
"warehouse_id": 1,
"quantity": "1.000000",
"base_quantity": "1.000000",
"unit_price": {
"amount": "100.00",
"currency": "GHS",
"formatted": "GH\u20b5100.00"
},
"discount": {
"amount": "0.00",
"currency": "GHS",
"formatted": "GH\u20b50.00"
},
"tax": {
"amount": "0.00",
"currency": "GHS",
"formatted": "GH\u20b50.00"
},
"total": {
"amount": "100.00",
"currency": "GHS",
"formatted": "GH\u20b5100.00"
},
"serial_number": null,
"price_source": "base",
"tax_breakdown": []
}
],
"totals": {
"subtotal": {
"amount": "100.00",
"currency": "GHS",
"formatted": "GH\u20b5100.00"
},
"tax": {
"amount": "0.00",
"currency": "GHS",
"formatted": "GH\u20b50.00"
},
"discount": {
"amount": "0.00",
"currency": "GHS",
"formatted": "GH\u20b50.00"
},
"total": {
"amount": "100.00",
"currency": "GHS",
"formatted": "GH\u20b5100.00"
}
},
"converted_to_invoice_id": null,
"converted_to_sale_id": null,
"actions": {
"edit": true,
"send": true,
"accept": true,
"reject": true,
"duplicate": true,
"convert_to_invoice": false,
"convert_to_sale": false,
"pdf": true,
"email": true,
"share": false
},
"converted_sale": null
},
"result": {
"kind": "estimate",
"id": 2,
"estimate_number": "EST-202610-0002",
"revision": "opaque-example-revision",
"store_id": 1,
"warehouse_id": 1,
"status": "draft",
"is_expired": false,
"valid_until": "2026-11-05",
"created_at": "2026-10-04T12: 00: 00+00: 00",
"notes": null,
"terms": null,
"customer": {
"id": null,
"name": "Walk-in Customer"
},
"tax_treatment": "standard",
"items": [
{
"id": 1,
"item_id": 1,
"name": "Example Product",
"sku": "EXAMPLE-001",
"unit_id": null,
"warehouse_id": 1,
"quantity": "1.000000",
"base_quantity": "1.000000",
"unit_price": {
"amount": "100.00",
"currency": "GHS",
"formatted": "GH\u20b5100.00"
},
"discount": {
"amount": "0.00",
"currency": "GHS",
"formatted": "GH\u20b50.00"
},
"tax": {
"amount": "0.00",
"currency": "GHS",
"formatted": "GH\u20b50.00"
},
"total": {
"amount": "100.00",
"currency": "GHS",
"formatted": "GH\u20b5100.00"
},
"serial_number": null,
"price_source": "base",
"tax_breakdown": []
}
],
"totals": {
"subtotal": {
"amount": "100.00",
"currency": "GHS",
"formatted": "GH\u20b5100.00"
},
"tax": {
"amount": "0.00",
"currency": "GHS",
"formatted": "GH\u20b50.00"
},
"discount": {
"amount": "0.00",
"currency": "GHS",
"formatted": "GH\u20b50.00"
},
"total": {
"amount": "100.00",
"currency": "GHS",
"formatted": "GH\u20b5100.00"
}
},
"converted_to_invoice_id": null,
"converted_to_sale_id": null,
"actions": {
"edit": true,
"send": true,
"accept": true,
"reject": true,
"duplicate": true,
"convert_to_invoice": false,
"convert_to_sale": false,
"pdf": true,
"email": true,
"share": false
},
"converted_sale": null
}
}
}
}
{
"success": false,
"code": "validation_failed",
"message": "The given data was invalid.",
"errors": {
"store_id": [
"Select an accessible active store."
]
}
}
Requires invoices feature enabled, estimates.view and the named action permission. Exact persisted native location is immutable. Root and all effective lines must be accessible before pagination, reads, mutations and replay; only NULL estimate lines inherit the authorized root. Shared invoice quotation owns pricing, explicit manual_unit_price permission/configuration, discounts, taxes and UOM. Estimate supports standard/exempt tax, not invoice WHT. New native quantities have up to three places; read-only historical quantities/base retain six places. Actual storage bounds apply: estimates quantity/base DECIMAL(10,6), invoice/sale primary DECIMAL(15,3), base DECIMAL(15,6), and each money column; overflow or unrepresentable conversion is a safe 422, never silent rounding. Drafts, duplication and invoice conversion are stock neutral. INV/EST numbers use global atomic monthly sequences, including deleted history and full suffixes beyond four digits. Current expiration behavior is retained. Snapshots are server-owned without historical backfill. PDF and email actions require fresh estimates capability, view policy and full scope; estimates have no public share. Native email submission does not change estimate state. Conversion needs Sent/Accepted and unexpired eligibility. Invoice conversion creates Draft with no stock effect. Sale conversion additionally needs live POS/sales.create, an open owned session, explicit matching item/unit/quantity/customer cart (serial selection may complete unfinished lines), a fresh POS quote and confirmed configured manual tender. One outer permanent operation owns checkout; no nested runner or second pricing algorithm. Same-key replay returns original root/owned-child IDs and current authorized resources, requiring both destination capability/permission and complete child scope. Duplication retains immutable source linkage and leaves original state unchanged.
| Parameter | In | Type | Description |
|---|---|---|---|
| estimate *required | path | string | Path parameter: Estimate |
| store_id *required | body | integer | Selected accessible active store. |
| warehouse_id | body | integer | Optional selected accessible active warehouse. |
| operation_key *required | body | string | Permanent actor-scoped UUID for this exact estimate action. |
| revision *required | body | string | Current estimate revision; replay precedes state and revision checks. |
{
"store_id": 1,
"warehouse_id": 1,
"operation_key": "00000000-0000-4000-8000-000000000001",
"revision": "opaque-example-revision"
}
{
"success": true,
"data": {
"operation": {
"key": "00000000-0000-4000-8000-000000000001",
"replayed": false
},
"result_ids": {
"estimate_id": 1
},
"resource": {
"kind": "estimate",
"id": 1,
"estimate_number": "EST-202610-0001",
"revision": "opaque-example-revision",
"store_id": 1,
"warehouse_id": 1,
"status": "rejected",
"is_expired": false,
"valid_until": "2026-11-05",
"created_at": "2026-10-04T12: 00: 00+00: 00",
"notes": null,
"terms": null,
"customer": {
"id": null,
"name": "Walk-in Customer"
},
"tax_treatment": "standard",
"items": [
{
"id": 1,
"item_id": 1,
"name": "Example Product",
"sku": "EXAMPLE-001",
"unit_id": null,
"warehouse_id": 1,
"quantity": "1.000000",
"base_quantity": "1.000000",
"unit_price": {
"amount": "100.00",
"currency": "GHS",
"formatted": "GH\u20b5100.00"
},
"discount": {
"amount": "0.00",
"currency": "GHS",
"formatted": "GH\u20b50.00"
},
"tax": {
"amount": "0.00",
"currency": "GHS",
"formatted": "GH\u20b50.00"
},
"total": {
"amount": "100.00",
"currency": "GHS",
"formatted": "GH\u20b5100.00"
},
"serial_number": null,
"price_source": "base",
"tax_breakdown": []
}
],
"totals": {
"subtotal": {
"amount": "100.00",
"currency": "GHS",
"formatted": "GH\u20b5100.00"
},
"tax": {
"amount": "0.00",
"currency": "GHS",
"formatted": "GH\u20b50.00"
},
"discount": {
"amount": "0.00",
"currency": "GHS",
"formatted": "GH\u20b50.00"
},
"total": {
"amount": "100.00",
"currency": "GHS",
"formatted": "GH\u20b5100.00"
}
},
"converted_to_invoice_id": null,
"converted_to_sale_id": null,
"actions": {
"edit": false,
"send": false,
"accept": false,
"reject": false,
"duplicate": true,
"convert_to_invoice": false,
"convert_to_sale": false,
"pdf": true,
"email": true,
"share": false
},
"converted_sale": null
}
}
}
{
"success": false,
"code": "validation_failed",
"message": "The given data was invalid.",
"errors": {
"store_id": [
"Select an accessible active store."
]
}
}
Requires invoices feature enabled, estimates.view and the named action permission. Exact persisted native location is immutable. Root and all effective lines must be accessible before pagination, reads, mutations and replay; only NULL estimate lines inherit the authorized root. Shared invoice quotation owns pricing, explicit manual_unit_price permission/configuration, discounts, taxes and UOM. Estimate supports standard/exempt tax, not invoice WHT. New native quantities have up to three places; read-only historical quantities/base retain six places. Actual storage bounds apply: estimates quantity/base DECIMAL(10,6), invoice/sale primary DECIMAL(15,3), base DECIMAL(15,6), and each money column; overflow or unrepresentable conversion is a safe 422, never silent rounding. Drafts, duplication and invoice conversion are stock neutral. INV/EST numbers use global atomic monthly sequences, including deleted history and full suffixes beyond four digits. Current expiration behavior is retained. Snapshots are server-owned without historical backfill. PDF and email actions require fresh estimates capability, view policy and full scope; estimates have no public share. Native email submission does not change estimate state. Conversion needs Sent/Accepted and unexpired eligibility. Invoice conversion creates Draft with no stock effect. Sale conversion additionally needs live POS/sales.create, an open owned session, explicit matching item/unit/quantity/customer cart (serial selection may complete unfinished lines), a fresh POS quote and confirmed configured manual tender. One outer permanent operation owns checkout; no nested runner or second pricing algorithm. Same-key replay returns original root/owned-child IDs and current authorized resources, requiring both destination capability/permission and complete child scope. Duplication retains immutable source linkage and leaves original state unchanged.
| Parameter | In | Type | Description |
|---|---|---|---|
| estimate *required | path | string | Path parameter: Estimate |
| store_id *required | body | integer | Selected accessible active store. |
| warehouse_id | body | integer | Optional selected accessible active warehouse. |
| operation_key *required | body | string | Permanent actor-scoped UUID for this exact estimate action. |
| revision *required | body | string | Current estimate revision; replay precedes state and revision checks. |
{
"store_id": 1,
"warehouse_id": 1,
"operation_key": "00000000-0000-4000-8000-000000000001",
"revision": "opaque-example-revision"
}
{
"success": true,
"data": {
"operation": {
"key": "00000000-0000-4000-8000-000000000001",
"replayed": false
},
"result_ids": {
"estimate_id": 1
},
"resource": {
"kind": "estimate",
"id": 1,
"estimate_number": "EST-202610-0001",
"revision": "opaque-example-revision",
"store_id": 1,
"warehouse_id": 1,
"status": "sent",
"is_expired": false,
"valid_until": "2026-11-05",
"created_at": "2026-10-04T12: 00: 00+00: 00",
"notes": null,
"terms": null,
"customer": {
"id": null,
"name": "Walk-in Customer"
},
"tax_treatment": "standard",
"items": [
{
"id": 1,
"item_id": 1,
"name": "Example Product",
"sku": "EXAMPLE-001",
"unit_id": null,
"warehouse_id": 1,
"quantity": "1.000000",
"base_quantity": "1.000000",
"unit_price": {
"amount": "100.00",
"currency": "GHS",
"formatted": "GH\u20b5100.00"
},
"discount": {
"amount": "0.00",
"currency": "GHS",
"formatted": "GH\u20b50.00"
},
"tax": {
"amount": "0.00",
"currency": "GHS",
"formatted": "GH\u20b50.00"
},
"total": {
"amount": "100.00",
"currency": "GHS",
"formatted": "GH\u20b5100.00"
},
"serial_number": null,
"price_source": "base",
"tax_breakdown": []
}
],
"totals": {
"subtotal": {
"amount": "100.00",
"currency": "GHS",
"formatted": "GH\u20b5100.00"
},
"tax": {
"amount": "0.00",
"currency": "GHS",
"formatted": "GH\u20b50.00"
},
"discount": {
"amount": "0.00",
"currency": "GHS",
"formatted": "GH\u20b50.00"
},
"total": {
"amount": "100.00",
"currency": "GHS",
"formatted": "GH\u20b5100.00"
}
},
"converted_to_invoice_id": null,
"converted_to_sale_id": null,
"actions": {
"edit": true,
"send": false,
"accept": true,
"reject": true,
"duplicate": true,
"convert_to_invoice": true,
"convert_to_sale": true,
"pdf": true,
"email": true,
"share": false
},
"converted_sale": null
}
}
}
{
"success": false,
"code": "validation_failed",
"message": "The given data was invalid.",
"errors": {
"store_id": [
"Select an accessible active store."
]
}
}
Requires invoices feature enabled, invoices.view and the named action permission. Whole invoice, linked sale and every line must be in the live selected location scope. Only NULL invoice line warehouses inherit the authorized invoice root; explicit foreign lines and linked-sale scope remain blocked. Native persisted location is immutable; relocation uses the existing authorized web workflow. Laravel owns prices, quantities, tax/WHT snapshots, discounts, payments and balances. Drafts never consume or reserve stock. Unverifiable legacy issued consumption blocks stock-affecting replacement, not metadata-only edits. Finalize and cancel actions reflect current policy and linked-document eligibility; stock is revalidated under locks, not guaranteed by the action flag. Payment requires eligible state, permission and an active configured manual method; payment_methods is independent of POS capability, and current eligibility is rechecked under locks. PDF and email actions require fresh invoices capability, view policy and full scope; share additionally requires existing share configuration. Document retry never repeats finance. Snapshot names/SKUs survive missing relations; old records have no backfill.
| Parameter | In | Type | Description |
|---|---|---|---|
| store_id *required | query | integer | Selected accessible active store. |
| warehouse_id | query | integer | Optional selected accessible active warehouse. |
| q | query | string | Literal number/customer search, maximum 100 characters. |
| page | query | integer | Page starting at one. |
| per_page | query | integer | Default 25; maximum 50. |
| status | query | string | Document state. |
| date_from | query | string | Inclusive created date YYYY-MM-DD. |
| date_to | query | string | Inclusive created date YYYY-MM-DD, not before date_from. |
{
"store_id": 1,
"warehouse_id": 1,
"q": "example",
"page": 1,
"per_page": 1,
"status": "example",
"date_from": "example",
"date_to": "example"
}
{
"success": true,
"data": {
"items": [
{
"kind": "invoice",
"id": 1,
"invoice_number": "INV-EXAMPLE-001",
"revision": "opaque-example-revision",
"store_id": 1,
"warehouse_id": 1,
"status": "draft",
"customer": {
"id": null,
"name": null
},
"due_date": null,
"created_at": "2026-10-04T12: 00: 00+00: 00",
"notes": null,
"terms": null,
"tax_treatment": "standard",
"items": [
{
"id": 1,
"item_id": 1,
"name": "Example Product",
"sku": "EXAMPLE-001",
"unit_id": null,
"warehouse_id": 1,
"quantity": "1.000",
"base_quantity": "1.000000",
"unit_price": {
"amount": "100.00",
"currency": "GHS",
"formatted": "GH\u20b5100.00"
},
"discount": {
"amount": "0.00",
"currency": "GHS",
"formatted": "GH\u20b50.00"
},
"tax": {
"amount": "0.00",
"currency": "GHS",
"formatted": "GH\u20b50.00"
},
"total": {
"amount": "100.00",
"currency": "GHS",
"formatted": "GH\u20b5100.00"
},
"serial_number": null,
"price_source": "base",
"tax_breakdown": []
}
],
"totals": {
"subtotal": {
"amount": "100.00",
"currency": "GHS",
"formatted": "GH\u20b5100.00"
},
"tax": {
"amount": "0.00",
"currency": "GHS",
"formatted": "GH\u20b50.00"
},
"discount": {
"amount": "0.00",
"currency": "GHS",
"formatted": "GH\u20b50.00"
},
"total": {
"amount": "100.00",
"currency": "GHS",
"formatted": "GH\u20b5100.00"
},
"withholding_tax": {
"amount": "0.00",
"currency": "GHS",
"formatted": "GH\u20b50.00"
},
"amount_paid": {
"amount": "0.00",
"currency": "GHS",
"formatted": "GH\u20b50.00"
},
"amount_due": {
"amount": "100.00",
"currency": "GHS",
"formatted": "GH\u20b5100.00"
}
},
"withholding_tax": {
"type_id": null,
"rate": null,
"base": null,
"reference": null,
"locked": false
},
"total_credited": {
"amount": "0.00",
"currency": "GHS",
"formatted": "GH\u20b50.00"
},
"total_refunded": {
"amount": "0.00",
"currency": "GHS",
"formatted": "GH\u20b50.00"
},
"net_collected": {
"amount": "0.00",
"currency": "GHS",
"formatted": "GH\u20b50.00"
},
"payments": [],
"payment_methods": [
{
"id": 1,
"name": "Cash",
"code": "cash",
"type": "cash",
"requires_reference": false
}
],
"actions": {
"edit": true,
"finalize": false,
"payment": false,
"cancel": false,
"pdf": true,
"email": true,
"share": true,
"create_credit": false,
"credit_history": true
},
"source_workflow": "invoice",
"source_sale": null,
"unavailable_reason": null
}
]
},
"meta": {
"current_page": 1,
"last_page": 1,
"per_page": 25,
"total": 1
}
}
{
"success": false,
"code": "validation_failed",
"message": "The given data was invalid.",
"errors": {
"store_id": [
"Select an accessible active store."
]
}
}
Requires invoices feature enabled, invoices.view and the named action permission. Whole invoice, linked sale and every line must be in the live selected location scope. Only NULL invoice line warehouses inherit the authorized invoice root; explicit foreign lines and linked-sale scope remain blocked. Native persisted location is immutable; relocation uses the existing authorized web workflow. Laravel owns prices, quantities, tax/WHT snapshots, discounts, payments and balances. Drafts never consume or reserve stock. Unverifiable legacy issued consumption blocks stock-affecting replacement, not metadata-only edits. Finalize and cancel actions reflect current policy and linked-document eligibility; stock is revalidated under locks, not guaranteed by the action flag. Payment requires eligible state, permission and an active configured manual method; payment_methods is independent of POS capability, and current eligibility is rechecked under locks. PDF and email actions require fresh invoices capability, view policy and full scope; share additionally requires existing share configuration. Document retry never repeats finance. Snapshot names/SKUs survive missing relations; old records have no backfill. Durable invoice.create returns only invoice_id and fresh authorized InvoiceResource; no automatic payment/finalization. Replay rechecks feature, permissions and complete scope.
| Parameter | In | Type | Description |
|---|---|---|---|
| store_id *required | body | integer | Selected accessible active store. |
| warehouse_id | body | integer | Optional selected accessible active warehouse. |
| invoice_id | body | integer | Quote an existing editable invoice; must match PATCH route if supplied. |
| operation_key *required | body | string | Permanent actor-scoped UUID. Same committed payload replays before quote expiry, mutable references and stale revision. |
| quote_token *required | body | string | Current five-minute invoice quote token. |
| customer_id | body | integer | Global customer ID; live existence checked after committed replay. |
| due_date | body | string | Due date YYYY-MM-DD. |
| notes | body | string | Invoice notes. |
| terms | body | string | Invoice terms. |
| items *required | body | array | Full line replacement. Required for creation; omit for metadata-only edit, retaining original lines and financial snapshots. |
| discount_type | body | string | Cart discount mode. |
| discount_value | body | string | Server converts percent intent to money, after line discounts and before subtracting from final total. |
| tax_treatment | body | string | Standard, exempt or no tax. |
| apply_withholding_tax | body | boolean | Apply active selected WHT type; type/rate/base lock after any payment. |
| withholding_tax_type_id | body | integer | Active WHT type, or current historical type on edit. |
| withholding_tax_reference | body | string | Optional reference, still editable when WHT type is locked. |
{
"store_id": 1,
"warehouse_id": 1,
"items": [
{
"item_id": 1,
"quantity": "1.000"
}
],
"operation_key": "00000000-0000-4000-8000-000000000001",
"quote_token": "SYNTHETIC-ENCRYPTED-QUOTE"
}
{
"success": true,
"data": {
"operation": {
"key": "00000000-0000-4000-8000-000000000001",
"replayed": false
},
"result_ids": {
"invoice_id": 1
},
"resource": {
"kind": "invoice",
"id": 1,
"invoice_number": "INV-EXAMPLE-001",
"revision": "opaque-example-revision",
"store_id": 1,
"warehouse_id": 1,
"status": "draft",
"customer": {
"id": null,
"name": null
},
"due_date": null,
"created_at": "2026-10-04T12: 00: 00+00: 00",
"notes": null,
"terms": null,
"tax_treatment": "standard",
"items": [
{
"id": 1,
"item_id": 1,
"name": "Example Product",
"sku": "EXAMPLE-001",
"unit_id": null,
"warehouse_id": 1,
"quantity": "1.000",
"base_quantity": "1.000000",
"unit_price": {
"amount": "100.00",
"currency": "GHS",
"formatted": "GH\u20b5100.00"
},
"discount": {
"amount": "0.00",
"currency": "GHS",
"formatted": "GH\u20b50.00"
},
"tax": {
"amount": "0.00",
"currency": "GHS",
"formatted": "GH\u20b50.00"
},
"total": {
"amount": "100.00",
"currency": "GHS",
"formatted": "GH\u20b5100.00"
},
"serial_number": null,
"price_source": "base",
"tax_breakdown": []
}
],
"totals": {
"subtotal": {
"amount": "100.00",
"currency": "GHS",
"formatted": "GH\u20b5100.00"
},
"tax": {
"amount": "0.00",
"currency": "GHS",
"formatted": "GH\u20b50.00"
},
"discount": {
"amount": "0.00",
"currency": "GHS",
"formatted": "GH\u20b50.00"
},
"total": {
"amount": "100.00",
"currency": "GHS",
"formatted": "GH\u20b5100.00"
},
"withholding_tax": {
"amount": "0.00",
"currency": "GHS",
"formatted": "GH\u20b50.00"
},
"amount_paid": {
"amount": "0.00",
"currency": "GHS",
"formatted": "GH\u20b50.00"
},
"amount_due": {
"amount": "100.00",
"currency": "GHS",
"formatted": "GH\u20b5100.00"
}
},
"withholding_tax": {
"type_id": null,
"rate": null,
"base": null,
"reference": null,
"locked": false
},
"total_credited": {
"amount": "0.00",
"currency": "GHS",
"formatted": "GH\u20b50.00"
},
"total_refunded": {
"amount": "0.00",
"currency": "GHS",
"formatted": "GH\u20b50.00"
},
"net_collected": {
"amount": "0.00",
"currency": "GHS",
"formatted": "GH\u20b50.00"
},
"payments": [],
"payment_methods": [
{
"id": 1,
"name": "Cash",
"code": "cash",
"type": "cash",
"requires_reference": false
}
],
"actions": {
"edit": true,
"finalize": false,
"payment": false,
"cancel": false,
"pdf": true,
"email": true,
"share": true,
"create_credit": false,
"credit_history": true
},
"source_workflow": "invoice",
"source_sale": null,
"unavailable_reason": null
}
}
}
{
"success": false,
"code": "validation_failed",
"message": "The given data was invalid.",
"errors": {
"store_id": [
"Select an accessible active store."
]
}
}
Global minimal customer lookup for invoice workflow. Requires invoices feature, invoices.view and either invoices.create or invoices.edit. Does not require POS feature/pos.access or expose customer history; customers have no warehouse ownership. Optional store/warehouse query fields do not filter global customers. Default 25, maximum 50 per page. Private no-store.
| Parameter | In | Type | Description |
|---|---|---|---|
| q | query | string | Catalog text or barcode alias, maximum 100 characters. |
| page | query | integer | Page >= 1. |
| per_page | query | integer | Default 25; maximum 50. Invalid values return 422. |
{
"q": "example",
"page": 1,
"per_page": 1
}
{
"success": true,
"data": {
"items": [
{
"kind": "customer",
"id": 1,
"revision": "opaque-example-revision",
"name": "Example Customer",
"email": "customer@example.invalid",
"phone": null,
"address": null,
"city": null,
"state": null,
"postal_code": null,
"country": null
}
]
},
"meta": {
"current_page": 1,
"last_page": 1,
"per_page": 25,
"total": 1
}
}
{
"success": false,
"code": "validation_failed",
"message": "The given data was invalid.",
"errors": {
"store_id": [
"Select an accessible active store."
]
}
}
Private no-store invoice editor options independent of POS. Fresh invoice capability and editor authority plus valid selected scope. Only active existing WHT types when the current WHT setting is enabled. Decimal rate is display-only; no client rate/base override or historical type rewrite.
| Parameter | In | Type | Description |
|---|---|---|---|
| store_id *required | query | integer | Selected accessible active store. |
| warehouse_id | query | integer | Optional selected accessible active warehouse. |
{
"store_id": 1,
"warehouse_id": 1
}
{
"success": true,
"data": {
"withholding_tax_types": [
{
"id": 1,
"name": "Example WHT",
"rate": "3.00"
}
],
"tax_treatments": [
"standard",
"exempt",
"none"
],
"manual_price_allowed": true
}
}
{
"success": false,
"code": "validation_failed",
"message": "The given data was invalid.",
"errors": {
"store_id": [
"Select an accessible active store."
]
}
}
Requires invoices feature enabled, invoices.view and the named action permission. Whole invoice, linked sale and every line must be in the live selected location scope. Only NULL invoice line warehouses inherit the authorized invoice root; explicit foreign lines and linked-sale scope remain blocked. Native persisted location is immutable; relocation uses the existing authorized web workflow. Laravel owns prices, quantities, tax/WHT snapshots, discounts, payments and balances. Drafts never consume or reserve stock. Unverifiable legacy issued consumption blocks stock-affecting replacement, not metadata-only edits. Finalize and cancel actions reflect current policy and linked-document eligibility; stock is revalidated under locks, not guaranteed by the action flag. Payment requires eligible state, permission and an active configured manual method; payment_methods is independent of POS capability, and current eligibility is rechecked under locks. PDF and email actions require fresh invoices capability, view policy and full scope; share additionally requires existing share configuration. Document retry never repeats finance. Snapshot names/SKUs survive missing relations; old records have no backfill. Active global invoice catalog, including configured services, independent of POS feature/access. stock_quantity is scoped on-hand (available serial count), not a reservation or finalization guarantee; six decimal places. Prices and active sale units are server-owned.
| Parameter | In | Type | Description |
|---|---|---|---|
| store_id *required | query | integer | Selected accessible active store. |
| warehouse_id | query | integer | Optional selected accessible active warehouse. |
| q | query | string | Literal number/customer search, maximum 100 characters. |
| page | query | integer | Page starting at one. |
| per_page | query | integer | Default 25; maximum 50. |
| customer_id | query | integer | Customer pricing context. |
{
"store_id": 1,
"warehouse_id": 1,
"q": "example",
"page": 1,
"per_page": 1,
"customer_id": 1
}
{
"success": true,
"data": {
"items": [
{
"id": 1,
"name": "Example Product",
"sku": "EXAMPLE-001",
"price": {
"amount": "100.00",
"currency": "GHS",
"formatted": "GH\u20b5100.00"
},
"sale_units": [
{
"id": 1,
"name": "Piece",
"code": "pc",
"conversion_factor": "1.000000",
"price": {
"amount": "100.00",
"currency": "GHS",
"formatted": "GH\u20b5100.00"
}
}
],
"is_service": false,
"is_serialized": false,
"stock_quantity": "10.000000",
"manual_price_allowed": true
}
]
},
"meta": {
"current_page": 1,
"last_page": 1,
"per_page": 25,
"total": 1
}
}
{
"success": false,
"code": "validation_failed",
"message": "The given data was invalid.",
"errors": {
"store_id": [
"Select an accessible active store."
]
}
}
Exact active item ID lookup sharing the existing invoice catalog price, unit, configured-service and scoped-stock projection. Fresh respective capability/editor authority/current location; no POS prerequisite, reservation or financial write. Private no-store.
| Parameter | In | Type | Description |
|---|---|---|---|
| item *required | path | string | Path parameter: Item |
| store_id *required | body | integer | Selected accessible active store. |
| warehouse_id | body | integer | Optional selected accessible active warehouse. |
| customer_id | query | integer | Current customer pricing context. |
{
"store_id": 1,
"warehouse_id": 1,
"customer_id": 1
}
{
"success": true,
"data": {
"id": 1,
"name": "Example Product",
"sku": "EXAMPLE-001",
"price": {
"amount": "100.00",
"currency": "GHS",
"formatted": "GH\u20b5100.00"
},
"sale_units": [
{
"id": 1,
"name": "Piece",
"code": "pc",
"conversion_factor": "1.000000",
"price": {
"amount": "100.00",
"currency": "GHS",
"formatted": "GH\u20b5100.00"
}
}
],
"is_service": false,
"is_serialized": false,
"stock_quantity": "10.000000",
"manual_price_allowed": true
}
}
{
"success": false,
"code": "validation_failed",
"message": "The given data was invalid.",
"errors": {
"store_id": [
"Select an accessible active store."
]
}
}
Fresh invoices capability and create/edit authority at the valid selected scope. Reuses available serial and reservation filtering; no POS capability, reservation or stock mutation. Bounded SerialPage and private no-store. Workflow is selected by the registered route, not client input.
| Parameter | In | Type | Description |
|---|---|---|---|
| item *required | path | string | Path parameter: Item |
| store_id *required | query | integer | Selected accessible active store. |
| warehouse_id | query | integer | Optional selected accessible active warehouse. |
| q | query | string | Catalog text or barcode alias, maximum 100 characters. |
| category_id | query | integer | Category filter. |
| customer_id | query | integer | Global customer selected for effective server pricing. |
| page | query | integer | Page >= 1. |
| per_page | query | integer | Default 25; maximum 50. Invalid values return 422. |
{
"store_id": 1,
"warehouse_id": 1,
"q": "example",
"category_id": 1,
"customer_id": 1,
"page": 1,
"per_page": 1
}
{
"success": true,
"data": {
"serials": [
{
"id": 1,
"serial_number": "EXAMPLE-SERIAL"
}
]
},
"meta": {
"current_page": 1,
"last_page": 1,
"per_page": 25,
"total": 1
}
}
{
"success": false,
"code": "validation_failed",
"message": "The given data was invalid.",
"errors": {
"store_id": [
"Select an accessible active store."
]
}
}
Requires invoices feature enabled, invoices.view and the named action permission. Whole invoice, linked sale and every line must be in the live selected location scope. Only NULL invoice line warehouses inherit the authorized invoice root; explicit foreign lines and linked-sale scope remain blocked. Native persisted location is immutable; relocation uses the existing authorized web workflow. Laravel owns prices, quantities, tax/WHT snapshots, discounts, payments and balances. Drafts never consume or reserve stock. Unverifiable legacy issued consumption blocks stock-affecting replacement, not metadata-only edits. Finalize and cancel actions reflect current policy and linked-document eligibility; stock is revalidated under locks, not guaranteed by the action flag. Payment requires eligible state, permission and an active configured manual method; payment_methods is independent of POS capability, and current eligibility is rechecked under locks. PDF and email actions require fresh invoices capability, view policy and full scope; share additionally requires existing share configuration. Document retry never repeats finance. Snapshot names/SKUs survive missing relations; old records have no backfill. Encrypted five-minute quote binds actor, exact location, complete intent and current server result. Manual intent requires explicit manual_unit_price, the existing create/edit policy and nakpos.pricing.enforce_server_pricing=false. price_source, unit_price, base_quantity, costs, totals and snapshots are prohibited client authority.
| Parameter | In | Type | Description |
|---|---|---|---|
| store_id *required | body | integer | Selected accessible active store. |
| warehouse_id | body | integer | Optional selected accessible active warehouse. |
| invoice_id | body | integer | Quote an existing editable invoice; must match PATCH route if supplied. |
| customer_id | body | integer | Global customer ID; live existence checked after committed replay. |
| items | body | array | Full line replacement. Required for creation; omit for metadata-only edit, retaining original lines and financial snapshots. |
| tax_treatment | body | string | Standard, exempt or no tax. |
| discount_type | body | string | Cart discount mode. |
| discount_value | body | string | Server converts percent intent to money, after line discounts and before subtracting from final total. |
| apply_withholding_tax | body | boolean | Apply active selected WHT type; type/rate/base lock after any payment. |
| withholding_tax_type_id | body | integer | Active WHT type, or current historical type on edit. |
| withholding_tax_reference | body | string | Optional reference, still editable when WHT type is locked. |
| due_date | body | string | Due date YYYY-MM-DD. |
| notes | body | string | Invoice notes. |
| terms | body | string | Invoice terms. |
{
"store_id": 1,
"warehouse_id": 1,
"items": [
{
"item_id": 1,
"quantity": "1.000"
}
]
}
{
"success": true,
"data": {
"lines": [
{
"item_id": 1,
"name": "Example Product",
"sku": "EXAMPLE-001",
"unit_id": null,
"quantity": "1.000",
"base_quantity": "1.000000",
"unit_price": {
"amount": "100.00",
"currency": "GHS",
"formatted": "GH\u20b5100.00"
},
"discount": {
"amount": "0.00",
"currency": "GHS",
"formatted": "GH\u20b50.00"
},
"tax": {
"amount": "0.00",
"currency": "GHS",
"formatted": "GH\u20b50.00"
},
"total": {
"amount": "100.00",
"currency": "GHS",
"formatted": "GH\u20b5100.00"
},
"serial_number": null,
"price_source": "base",
"tax_breakdown": []
}
],
"totals": {
"subtotal": {
"amount": "100.00",
"currency": "GHS",
"formatted": "GH\u20b5100.00"
},
"tax": {
"amount": "0.00",
"currency": "GHS",
"formatted": "GH\u20b50.00"
},
"discount": {
"amount": "0.00",
"currency": "GHS",
"formatted": "GH\u20b50.00"
},
"total": {
"amount": "100.00",
"currency": "GHS",
"formatted": "GH\u20b5100.00"
},
"withholding_tax": {
"amount": "0.00",
"currency": "GHS",
"formatted": "GH\u20b50.00"
},
"amount_paid": {
"amount": "0.00",
"currency": "GHS",
"formatted": "GH\u20b50.00"
},
"amount_due": {
"amount": "100.00",
"currency": "GHS",
"formatted": "GH\u20b5100.00"
}
},
"tax_treatment": "standard",
"manual_price_allowed": true,
"revision": null,
"quote_token": "SYNTHETIC-ENCRYPTED-QUOTE",
"expires_at": "2026-10-05T12: 05: 00+00: 00"
}
}
{
"success": false,
"code": "validation_failed",
"message": "The given data was invalid.",
"errors": {
"store_id": [
"Select an accessible active store."
]
}
}
Requires invoices feature enabled, invoices.view and the named action permission. Whole invoice, linked sale and every line must be in the live selected location scope. Only NULL invoice line warehouses inherit the authorized invoice root; explicit foreign lines and linked-sale scope remain blocked. Native persisted location is immutable; relocation uses the existing authorized web workflow. Laravel owns prices, quantities, tax/WHT snapshots, discounts, payments and balances. Drafts never consume or reserve stock. Unverifiable legacy issued consumption blocks stock-affecting replacement, not metadata-only edits. Finalize and cancel actions reflect current policy and linked-document eligibility; stock is revalidated under locks, not guaranteed by the action flag. Payment requires eligible state, permission and an active configured manual method; payment_methods is independent of POS capability, and current eligibility is rechecked under locks. PDF and email actions require fresh invoices capability, view policy and full scope; share additionally requires existing share configuration. Document retry never repeats finance. Snapshot names/SKUs survive missing relations; old records have no backfill.
| Parameter | In | Type | Description |
|---|---|---|---|
| invoice *required | path | string | Path parameter: Invoice |
| store_id *required | query | integer | Selected accessible active store. |
| warehouse_id | query | integer | Optional selected accessible active warehouse. |
{
"store_id": 1,
"warehouse_id": 1
}
{
"success": true,
"data": {
"kind": "invoice",
"id": 1,
"invoice_number": "INV-EXAMPLE-001",
"revision": "opaque-example-revision",
"store_id": 1,
"warehouse_id": 1,
"status": "draft",
"customer": {
"id": null,
"name": null
},
"due_date": null,
"created_at": "2026-10-04T12: 00: 00+00: 00",
"notes": null,
"terms": null,
"tax_treatment": "standard",
"items": [
{
"id": 1,
"item_id": 1,
"name": "Example Product",
"sku": "EXAMPLE-001",
"unit_id": null,
"warehouse_id": 1,
"quantity": "1.000",
"base_quantity": "1.000000",
"unit_price": {
"amount": "100.00",
"currency": "GHS",
"formatted": "GH\u20b5100.00"
},
"discount": {
"amount": "0.00",
"currency": "GHS",
"formatted": "GH\u20b50.00"
},
"tax": {
"amount": "0.00",
"currency": "GHS",
"formatted": "GH\u20b50.00"
},
"total": {
"amount": "100.00",
"currency": "GHS",
"formatted": "GH\u20b5100.00"
},
"serial_number": null,
"price_source": "base",
"tax_breakdown": []
}
],
"totals": {
"subtotal": {
"amount": "100.00",
"currency": "GHS",
"formatted": "GH\u20b5100.00"
},
"tax": {
"amount": "0.00",
"currency": "GHS",
"formatted": "GH\u20b50.00"
},
"discount": {
"amount": "0.00",
"currency": "GHS",
"formatted": "GH\u20b50.00"
},
"total": {
"amount": "100.00",
"currency": "GHS",
"formatted": "GH\u20b5100.00"
},
"withholding_tax": {
"amount": "0.00",
"currency": "GHS",
"formatted": "GH\u20b50.00"
},
"amount_paid": {
"amount": "0.00",
"currency": "GHS",
"formatted": "GH\u20b50.00"
},
"amount_due": {
"amount": "100.00",
"currency": "GHS",
"formatted": "GH\u20b5100.00"
}
},
"withholding_tax": {
"type_id": null,
"rate": null,
"base": null,
"reference": null,
"locked": false
},
"total_credited": {
"amount": "0.00",
"currency": "GHS",
"formatted": "GH\u20b50.00"
},
"total_refunded": {
"amount": "0.00",
"currency": "GHS",
"formatted": "GH\u20b50.00"
},
"net_collected": {
"amount": "0.00",
"currency": "GHS",
"formatted": "GH\u20b50.00"
},
"payments": [],
"payment_methods": [
{
"id": 1,
"name": "Cash",
"code": "cash",
"type": "cash",
"requires_reference": false
}
],
"actions": {
"edit": true,
"finalize": false,
"payment": false,
"cancel": false,
"pdf": true,
"email": true,
"share": true,
"create_credit": false,
"credit_history": true
},
"source_workflow": "invoice",
"source_sale": null,
"unavailable_reason": null
}
}
{
"success": false,
"code": "validation_failed",
"message": "The given data was invalid.",
"errors": {
"store_id": [
"Select an accessible active store."
]
}
}
Requires invoices feature enabled, invoices.view and the named action permission. Whole invoice, linked sale and every line must be in the live selected location scope. Only NULL invoice line warehouses inherit the authorized invoice root; explicit foreign lines and linked-sale scope remain blocked. Native persisted location is immutable; relocation uses the existing authorized web workflow. Laravel owns prices, quantities, tax/WHT snapshots, discounts, payments and balances. Drafts never consume or reserve stock. Unverifiable legacy issued consumption blocks stock-affecting replacement, not metadata-only edits. Finalize and cancel actions reflect current policy and linked-document eligibility; stock is revalidated under locks, not guaranteed by the action flag. Payment requires eligible state, permission and an active configured manual method; payment_methods is independent of POS capability, and current eligibility is rechecked under locks. PDF and email actions require fresh invoices capability, view policy and full scope; share additionally requires existing share configuration. Document retry never repeats finance. Snapshot names/SKUs survive missing relations; old records have no backfill. Durable invoice.update returns only invoice_id and fresh authorized InvoiceResource; no automatic payment/finalization. Replay rechecks feature, permissions and complete scope.
| Parameter | In | Type | Description |
|---|---|---|---|
| invoice *required | path | string | Path parameter: Invoice |
| store_id *required | body | integer | Selected accessible active store. |
| warehouse_id | body | integer | Optional selected accessible active warehouse. |
| invoice_id | body | integer | Quote an existing editable invoice; must match PATCH route if supplied. |
| operation_key *required | body | string | Permanent actor-scoped UUID. Same committed payload replays before quote expiry, mutable references and stale revision. |
| quote_token *required | body | string | Current five-minute invoice quote token. |
| revision *required | body | string | Fresh opaque invoice revision from quote or detail. |
| customer_id | body | integer | Global customer ID; live existence checked after committed replay. |
| due_date | body | string | Due date YYYY-MM-DD. |
| notes | body | string | Invoice notes. |
| terms | body | string | Invoice terms. |
| items | body | array | Full line replacement. Required for creation; omit for metadata-only edit, retaining original lines and financial snapshots. |
| discount_type | body | string | Cart discount mode. |
| discount_value | body | string | Server converts percent intent to money, after line discounts and before subtracting from final total. |
| tax_treatment | body | string | Standard, exempt or no tax. |
| apply_withholding_tax | body | boolean | Apply active selected WHT type; type/rate/base lock after any payment. |
| withholding_tax_type_id | body | integer | Active WHT type, or current historical type on edit. |
| withholding_tax_reference | body | string | Optional reference, still editable when WHT type is locked. |
{
"store_id": 1,
"warehouse_id": 1,
"items": [
{
"item_id": 1,
"quantity": "1.000"
}
],
"operation_key": "00000000-0000-4000-8000-000000000001",
"quote_token": "SYNTHETIC-ENCRYPTED-QUOTE",
"invoice_id": 1,
"revision": "opaque-example-revision"
}
{
"success": true,
"data": {
"operation": {
"key": "00000000-0000-4000-8000-000000000001",
"replayed": false
},
"result_ids": {
"invoice_id": 1
},
"resource": {
"kind": "invoice",
"id": 1,
"invoice_number": "INV-EXAMPLE-001",
"revision": "opaque-example-revision",
"store_id": 1,
"warehouse_id": 1,
"status": "draft",
"customer": {
"id": null,
"name": null
},
"due_date": null,
"created_at": "2026-10-04T12: 00: 00+00: 00",
"notes": null,
"terms": null,
"tax_treatment": "standard",
"items": [
{
"id": 1,
"item_id": 1,
"name": "Example Product",
"sku": "EXAMPLE-001",
"unit_id": null,
"warehouse_id": 1,
"quantity": "1.000",
"base_quantity": "1.000000",
"unit_price": {
"amount": "100.00",
"currency": "GHS",
"formatted": "GH\u20b5100.00"
},
"discount": {
"amount": "0.00",
"currency": "GHS",
"formatted": "GH\u20b50.00"
},
"tax": {
"amount": "0.00",
"currency": "GHS",
"formatted": "GH\u20b50.00"
},
"total": {
"amount": "100.00",
"currency": "GHS",
"formatted": "GH\u20b5100.00"
},
"serial_number": null,
"price_source": "base",
"tax_breakdown": []
}
],
"totals": {
"subtotal": {
"amount": "100.00",
"currency": "GHS",
"formatted": "GH\u20b5100.00"
},
"tax": {
"amount": "0.00",
"currency": "GHS",
"formatted": "GH\u20b50.00"
},
"discount": {
"amount": "0.00",
"currency": "GHS",
"formatted": "GH\u20b50.00"
},
"total": {
"amount": "100.00",
"currency": "GHS",
"formatted": "GH\u20b5100.00"
},
"withholding_tax": {
"amount": "0.00",
"currency": "GHS",
"formatted": "GH\u20b50.00"
},
"amount_paid": {
"amount": "0.00",
"currency": "GHS",
"formatted": "GH\u20b50.00"
},
"amount_due": {
"amount": "100.00",
"currency": "GHS",
"formatted": "GH\u20b5100.00"
}
},
"withholding_tax": {
"type_id": null,
"rate": null,
"base": null,
"reference": null,
"locked": false
},
"total_credited": {
"amount": "0.00",
"currency": "GHS",
"formatted": "GH\u20b50.00"
},
"total_refunded": {
"amount": "0.00",
"currency": "GHS",
"formatted": "GH\u20b50.00"
},
"net_collected": {
"amount": "0.00",
"currency": "GHS",
"formatted": "GH\u20b50.00"
},
"payments": [],
"payment_methods": [
{
"id": 1,
"name": "Cash",
"code": "cash",
"type": "cash",
"requires_reference": false
}
],
"actions": {
"edit": true,
"finalize": false,
"payment": false,
"cancel": false,
"pdf": true,
"email": true,
"share": true,
"create_credit": false,
"credit_history": true
},
"source_workflow": "invoice",
"source_sale": null,
"unavailable_reason": null
}
}
}
{
"success": false,
"code": "validation_failed",
"message": "The given data was invalid.",
"errors": {
"store_id": [
"Select an accessible active store."
]
}
}
Requires invoices feature enabled, invoices.view and the named action permission. Whole invoice, linked sale and every line must be in the live selected location scope. Only NULL invoice line warehouses inherit the authorized invoice root; explicit foreign lines and linked-sale scope remain blocked. Native persisted location is immutable; relocation uses the existing authorized web workflow. Laravel owns prices, quantities, tax/WHT snapshots, discounts, payments and balances. Drafts never consume or reserve stock. Unverifiable legacy issued consumption blocks stock-affecting replacement, not metadata-only edits. Finalize and cancel actions reflect current policy and linked-document eligibility; stock is revalidated under locks, not guaranteed by the action flag. Payment requires eligible state, permission and an active configured manual method; payment_methods is independent of POS capability, and current eligibility is rechecked under locks. PDF and email actions require fresh invoices capability, view policy and full scope; share additionally requires existing share configuration. Document retry never repeats finance. Snapshot names/SKUs survive missing relations; old records have no backfill. Durable invoice.cancel returns only invoice_id and a fresh authorized InvoiceResource. Finalize changes Draft to Sent, consuming aggregate persisted base quantities once with distinct available serials and reservation/transfer checks, without repricing. Cancellation rejects paid/partial/any recorded payment and linked stock ambiguity; drafts have no stock effect, issued invoices restore only documented remaining consumption. Unknown fields are rejected. Notification failure cannot repeat committed stock effects.
| Parameter | In | Type | Description |
|---|---|---|---|
| invoice *required | path | string | Path parameter: Invoice |
| store_id *required | body | integer | Selected accessible active store. |
| warehouse_id | body | integer | Optional selected accessible active warehouse. |
| operation_key *required | body | string | Permanent actor-scoped UUID; replay before state/revision checks. |
| revision *required | body | string | Current invoice revision. New operation with stale revision returns 409. |
| reason *required | body | string | Cancellation audit reason, 1..1000 characters. No stock-restore bypass is accepted. |
{
"store_id": 1,
"warehouse_id": 1,
"operation_key": "00000000-0000-4000-8000-000000000001",
"revision": "opaque-example-revision",
"reason": "Customer cancelled"
}
{
"success": true,
"data": {
"operation": {
"key": "00000000-0000-4000-8000-000000000001",
"replayed": false
},
"result_ids": {
"invoice_id": 1
},
"resource": {
"kind": "invoice",
"id": 1,
"invoice_number": "INV-EXAMPLE-001",
"revision": "opaque-example-revision",
"store_id": 1,
"warehouse_id": 1,
"status": "cancelled",
"customer": {
"id": null,
"name": null
},
"due_date": null,
"created_at": "2026-10-04T12: 00: 00+00: 00",
"notes": null,
"terms": null,
"tax_treatment": "standard",
"items": [
{
"id": 1,
"item_id": 1,
"name": "Example Product",
"sku": "EXAMPLE-001",
"unit_id": null,
"warehouse_id": 1,
"quantity": "1.000",
"base_quantity": "1.000000",
"unit_price": {
"amount": "100.00",
"currency": "GHS",
"formatted": "GH\u20b5100.00"
},
"discount": {
"amount": "0.00",
"currency": "GHS",
"formatted": "GH\u20b50.00"
},
"tax": {
"amount": "0.00",
"currency": "GHS",
"formatted": "GH\u20b50.00"
},
"total": {
"amount": "100.00",
"currency": "GHS",
"formatted": "GH\u20b5100.00"
},
"serial_number": null,
"price_source": "base",
"tax_breakdown": []
}
],
"totals": {
"subtotal": {
"amount": "100.00",
"currency": "GHS",
"formatted": "GH\u20b5100.00"
},
"tax": {
"amount": "0.00",
"currency": "GHS",
"formatted": "GH\u20b50.00"
},
"discount": {
"amount": "0.00",
"currency": "GHS",
"formatted": "GH\u20b50.00"
},
"total": {
"amount": "100.00",
"currency": "GHS",
"formatted": "GH\u20b5100.00"
},
"withholding_tax": {
"amount": "0.00",
"currency": "GHS",
"formatted": "GH\u20b50.00"
},
"amount_paid": {
"amount": "0.00",
"currency": "GHS",
"formatted": "GH\u20b50.00"
},
"amount_due": {
"amount": "100.00",
"currency": "GHS",
"formatted": "GH\u20b5100.00"
}
},
"withholding_tax": {
"type_id": null,
"rate": null,
"base": null,
"reference": null,
"locked": false
},
"total_credited": {
"amount": "0.00",
"currency": "GHS",
"formatted": "GH\u20b50.00"
},
"total_refunded": {
"amount": "0.00",
"currency": "GHS",
"formatted": "GH\u20b50.00"
},
"net_collected": {
"amount": "0.00",
"currency": "GHS",
"formatted": "GH\u20b50.00"
},
"payments": [],
"payment_methods": [
{
"id": 1,
"name": "Cash",
"code": "cash",
"type": "cash",
"requires_reference": false
}
],
"actions": {
"edit": true,
"finalize": false,
"payment": false,
"cancel": false,
"pdf": true,
"email": true,
"share": true,
"create_credit": false,
"credit_history": true
},
"source_workflow": "invoice",
"source_sale": null,
"unavailable_reason": null
}
}
}
{
"success": false,
"code": "validation_failed",
"message": "The given data was invalid.",
"errors": {
"store_id": [
"Select an accessible active store."
]
}
}
Requires current after-sales capability and credit_notes.view plus action authority. Whole source invoice, note and target scopes are checked before serialization/pagination. Permanent UUID replay reauthorizes current result ownership before mutable revision/state checks. No provider calls, client approver/payable/tax/totals or original serial override. Cache-Control: private, no-store. Original stored invoice display/serial/unit data; nonserialized intent three places, base six places, serialized quantity/base one. Active or evidenced historical credit reserves quantity. Sale-linked invoices use the canonical Sale workflow and cannot create credits.
| Parameter | In | Type | Description |
|---|---|---|---|
| invoice *required | path | string | Path parameter: Invoice |
| store_id *required | query | integer | Selected accessible active store. |
| warehouse_id | query | integer | Optional selected accessible active warehouse. |
{
"store_id": 1,
"warehouse_id": 1
}
{
"success": true,
"data": {
"invoice_id": 1,
"revision": "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa",
"can_credit": true,
"reason": null,
"types": [
"return",
"price_adjustment",
"overpayment",
"goodwill"
],
"conditions": [
"resellable",
"damaged",
"defective",
"opened"
],
"items": [
{
"invoice_item_id": 1,
"name": "Stored Product",
"sku": "STORED-SKU",
"quantity": "2.000",
"creditable_quantity": "2.000",
"base_quantity": "2.000000",
"warehouse_id": 1,
"serial_number": null,
"serialized": false,
"unit": {
"id": 1,
"name": "pc"
},
"available": true
}
],
"source_workflow": "invoice",
"source_sale": null
}
}
{
"success": false,
"code": "validation_failed",
"message": "The given data was invalid.",
"errors": {
"store_id": [
"Select an accessible active store."
]
}
}
Requires current after-sales capability and credit_notes.view plus action authority. Whole source invoice, note and target scopes are checked before serialization/pagination. Permanent UUID replay reauthorizes current result ownership before mutable revision/state checks. No provider calls, client approver/payable/tax/totals or original serial override. Cache-Control: private, no-store. Manual refund acknowledges a ledger operation, never a gateway transfer. Shared web/native writers lock actor, sorted source/target invoice roots, note and stock/payment inputs before reauthorizing. Unknown fields rejected.
| Parameter | In | Type | Description |
|---|---|---|---|
| invoice *required | path | string | Path parameter: Invoice |
| store_id *required | body | integer | Selected accessible active store. |
| warehouse_id | body | integer | Optional selected accessible active warehouse. |
| operation_key *required | body | string | Permanent UUID. |
| revision *required | body | string | Current 64-character source Invoice revision for creation; CreditNote revision for all actions. |
| type *required | body | string | return,price_adjustment,overpayment,goodwill. |
| reason *required | body | string | Maximum2000 characters. |
| items *required | body | array | Distinct original invoice lines; max100. |
{
"store_id": 1,
"warehouse_id": 1,
"operation_key": "00000000-0000-4000-8000-000000000001",
"revision": "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa",
"type": "return",
"reason": "Example credit",
"items": [
{
"invoice_item_id": 1,
"quantity": "1.000",
"condition": "resellable",
"restock": true
}
]
}
{
"success": true,
"data": {
"operation": {
"key": "00000000-0000-4000-8000-000000000001",
"replayed": false
},
"result_ids": {
"invoice_id": 1,
"credit_note_id": 1
},
"resource": {
"kind": "credit_note",
"id": 1,
"invoice_id": 1,
"number": "CN-202610-0001",
"store_id": 1,
"warehouse_id": 1,
"type": "return",
"status": "draft",
"revision": "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa",
"customer": {
"id": 1,
"name": "Stored Customer"
},
"subtotal": {
"amount": "12.50",
"currency": "GHS",
"formatted": "GH\u20b512.50"
},
"tax": {
"amount": "0.00",
"currency": "GHS",
"formatted": "GH\u20b50.00"
},
"total": {
"amount": "12.50",
"currency": "GHS",
"formatted": "GH\u20b512.50"
},
"consumed": {
"amount": "0.00",
"currency": "GHS",
"formatted": "GH\u20b50.00"
},
"applied": {
"amount": "0.00",
"currency": "GHS",
"formatted": "GH\u20b50.00"
},
"refunded": {
"amount": "0.00",
"currency": "GHS",
"formatted": "GH\u20b50.00"
},
"remaining": {
"amount": "12.50",
"currency": "GHS",
"formatted": "GH\u20b512.50"
},
"reason": "Example credit",
"voided_reason": null,
"requires_approval": true,
"created_at": "2026-10-04T12: 00: 00+00: 00",
"approved_at": null,
"voided_at": null,
"items": [
{
"id": 1,
"invoice_item_id": 1,
"name": "Stored Product",
"sku": "STORED-SKU",
"warehouse_id": 1,
"quantity": "1.000",
"base_quantity": "1.000000",
"unit": {
"id": 1,
"name": "pc"
},
"serial_number": null,
"condition": "resellable",
"restock": true,
"restocked": false,
"amount": {
"amount": "12.50",
"currency": "GHS",
"formatted": "GH\u20b512.50"
},
"tax": {
"amount": "0.00",
"currency": "GHS",
"formatted": "GH\u20b50.00"
}
}
],
"applications": [],
"refund_plan": {
"available": false,
"requires_gateway": false,
"maximum": {
"amount": "0.00",
"currency": "GHS",
"formatted": "GH\u20b50.00"
},
"methods": [
{
"id": 1,
"name": "Cash",
"code": "cash"
}
]
},
"allowed_actions": {
"submit": true,
"approve": false,
"reject": false,
"void": true,
"apply": false,
"refund": false
},
"source_workflow": "invoice",
"unavailable_reason": null,
"source_sale": null
}
}
}
{
"success": false,
"code": "validation_failed",
"message": "The given data was invalid.",
"errors": {
"store_id": [
"Select an accessible active store."
]
}
}
Current actor and exact selected location, fresh capability and whole parent scope. Payment receipt additionally requires the explicit Invoice parent and retained Payment ownership. Maximum 10 linked statuses; blocking pending/queued/processing/uncertain prioritized before terminal statuses so old uncertain sends remain visible. No recipient, body or provider reference. Read-only and private no-store; never resend while blocking or unknown.
| Parameter | In | Type | Description |
|---|---|---|---|
| invoice *required | path | string | Path parameter: Invoice |
| store_id *required | query | integer | Selected accessible active store. |
| warehouse_id | query | integer | Optional selected accessible active warehouse. |
{
"store_id": 1,
"warehouse_id": 1
}
{
"success": true,
"data": {
"items": [
{
"kind": "document_delivery",
"id": 1,
"document_kind": "invoice",
"document_id": 1,
"status": "pending",
"queued_at": null,
"attempted_at": null,
"accepted_at": null,
"error_code": null
}
]
}
}
{
"success": false,
"code": "validation_failed",
"message": "The given data was invalid.",
"errors": {
"store_id": [
"Select an accessible active store."
]
}
}
Requires invoices feature enabled, invoices.view and the named action permission. Whole invoice, linked sale and every line must be in the live selected location scope. Only NULL invoice line warehouses inherit the authorized invoice root; explicit foreign lines and linked-sale scope remain blocked. Native persisted location is immutable; relocation uses the existing authorized web workflow. Laravel owns prices, quantities, tax/WHT snapshots, discounts, payments and balances. Drafts never consume or reserve stock. Unverifiable legacy issued consumption blocks stock-affecting replacement, not metadata-only edits. Finalize and cancel actions reflect current policy and linked-document eligibility; stock is revalidated under locks, not guaranteed by the action flag. Payment requires eligible state, permission and an active configured manual method; payment_methods is independent of POS capability, and current eligibility is rechecked under locks. PDF and email actions require fresh invoices capability, view policy and full scope; share additionally requires existing share configuration. Document retry never repeats finance. Snapshot names/SKUs survive missing relations; old records have no backfill. Durable invoice.finalize returns only invoice_id and a fresh authorized InvoiceResource. Finalize changes Draft to Sent, consuming aggregate persisted base quantities once with distinct available serials and reservation/transfer checks, without repricing. Cancellation rejects paid/partial/any recorded payment and linked stock ambiguity; drafts have no stock effect, issued invoices restore only documented remaining consumption. Unknown fields are rejected. Notification failure cannot repeat committed stock effects.
| Parameter | In | Type | Description |
|---|---|---|---|
| invoice *required | path | string | Path parameter: Invoice |
| store_id *required | body | integer | Selected accessible active store. |
| warehouse_id | body | integer | Optional selected accessible active warehouse. |
| operation_key *required | body | string | Permanent actor-scoped UUID; replay before state/revision checks. |
| revision *required | body | string | Current invoice revision. New operation with stale revision returns 409. |
{
"store_id": 1,
"warehouse_id": 1,
"operation_key": "00000000-0000-4000-8000-000000000001",
"revision": "opaque-example-revision"
}
{
"success": true,
"data": {
"operation": {
"key": "00000000-0000-4000-8000-000000000001",
"replayed": false
},
"result_ids": {
"invoice_id": 1
},
"resource": {
"kind": "invoice",
"id": 1,
"invoice_number": "INV-EXAMPLE-001",
"revision": "opaque-example-revision",
"store_id": 1,
"warehouse_id": 1,
"status": "sent",
"customer": {
"id": null,
"name": null
},
"due_date": null,
"created_at": "2026-10-04T12: 00: 00+00: 00",
"notes": null,
"terms": null,
"tax_treatment": "standard",
"items": [
{
"id": 1,
"item_id": 1,
"name": "Example Product",
"sku": "EXAMPLE-001",
"unit_id": null,
"warehouse_id": 1,
"quantity": "1.000",
"base_quantity": "1.000000",
"unit_price": {
"amount": "100.00",
"currency": "GHS",
"formatted": "GH\u20b5100.00"
},
"discount": {
"amount": "0.00",
"currency": "GHS",
"formatted": "GH\u20b50.00"
},
"tax": {
"amount": "0.00",
"currency": "GHS",
"formatted": "GH\u20b50.00"
},
"total": {
"amount": "100.00",
"currency": "GHS",
"formatted": "GH\u20b5100.00"
},
"serial_number": null,
"price_source": "base",
"tax_breakdown": []
}
],
"totals": {
"subtotal": {
"amount": "100.00",
"currency": "GHS",
"formatted": "GH\u20b5100.00"
},
"tax": {
"amount": "0.00",
"currency": "GHS",
"formatted": "GH\u20b50.00"
},
"discount": {
"amount": "0.00",
"currency": "GHS",
"formatted": "GH\u20b50.00"
},
"total": {
"amount": "100.00",
"currency": "GHS",
"formatted": "GH\u20b5100.00"
},
"withholding_tax": {
"amount": "0.00",
"currency": "GHS",
"formatted": "GH\u20b50.00"
},
"amount_paid": {
"amount": "0.00",
"currency": "GHS",
"formatted": "GH\u20b50.00"
},
"amount_due": {
"amount": "100.00",
"currency": "GHS",
"formatted": "GH\u20b5100.00"
}
},
"withholding_tax": {
"type_id": null,
"rate": null,
"base": null,
"reference": null,
"locked": false
},
"total_credited": {
"amount": "0.00",
"currency": "GHS",
"formatted": "GH\u20b50.00"
},
"total_refunded": {
"amount": "0.00",
"currency": "GHS",
"formatted": "GH\u20b50.00"
},
"net_collected": {
"amount": "0.00",
"currency": "GHS",
"formatted": "GH\u20b50.00"
},
"payments": [],
"payment_methods": [
{
"id": 1,
"name": "Cash",
"code": "cash",
"type": "cash",
"requires_reference": false
}
],
"actions": {
"edit": true,
"finalize": false,
"payment": true,
"cancel": true,
"pdf": true,
"email": true,
"share": true,
"create_credit": false,
"credit_history": true
},
"source_workflow": "invoice",
"source_sale": null,
"unavailable_reason": null
}
}
}
{
"success": false,
"code": "validation_failed",
"message": "The given data was invalid.",
"errors": {
"store_id": [
"Select an accessible active store."
]
}
}
Requires invoices feature enabled, invoices.view and the named action permission. Whole invoice, linked sale and every line must be in the live selected location scope. Only NULL invoice line warehouses inherit the authorized invoice root; explicit foreign lines and linked-sale scope remain blocked. Native persisted location is immutable; relocation uses the existing authorized web workflow. Laravel owns prices, quantities, tax/WHT snapshots, discounts, payments and balances. Drafts never consume or reserve stock. Unverifiable legacy issued consumption blocks stock-affecting replacement, not metadata-only edits. Finalize and cancel actions reflect current policy and linked-document eligibility; stock is revalidated under locks, not guaranteed by the action flag. Payment requires eligible state, permission and an active configured manual method; payment_methods is independent of POS capability, and current eligibility is rechecked under locks. PDF and email actions require fresh invoices capability, view policy and full scope; share additionally requires existing share configuration. Document retry never repeats finance. Snapshot names/SKUs survive missing relations; old records have no backfill. Records manual money only, never initializes a gateway or consumes stock again. Sent/Partial/Overdue states only; Draft/Cancelled/Paid and overpayment reject. Existing WHT and optional reference rules remain. Server supplies date, receiver, paid amount and balance. Durable result contains exactly invoice_id/payment_id; replay and reconciliation verify the retained child belongs to the authorized parent. Payment reference/notes are never retained in operation IDs or returned. Events dispatch after outermost commit; notification failure cannot repeat money.
| Parameter | In | Type | Description |
|---|---|---|---|
| invoice *required | path | string | Path parameter: Invoice |
| store_id *required | body | integer | Selected accessible active store. |
| warehouse_id | body | integer | Optional selected accessible active warehouse. |
| operation_key *required | body | string | Permanent actor-scoped UUID for invoice.payment. Identical committed replay precedes stale revision, state, balance and method validation. |
| revision *required | body | string | Current invoice revision. |
| payment_method_id *required | body | integer | Existing active store-configured manual method. Online, gateway and credit methods are rejected. |
| amount *required | body | string | Positive externally received amount, no more than the current balance after WHT. |
| reference | body | string | Optional invoice payment reference, including noncash. |
| notes | body | string | Private payment notes, omitted from the response. |
| payment_confirmed *required | body | boolean | Explicit confirmation that money was already received. |
{
"store_id": 1,
"warehouse_id": 1,
"operation_key": "00000000-0000-4000-8000-000000000001",
"revision": "opaque-example-revision",
"payment_method_id": 1,
"amount": "40.00",
"payment_confirmed": true
}
{
"success": true,
"data": {
"operation": {
"key": "00000000-0000-4000-8000-000000000001",
"replayed": false
},
"result_ids": {
"invoice_id": 1,
"payment_id": 1
},
"resource": {
"kind": "invoice_payment",
"invoice": {
"kind": "invoice",
"id": 1,
"invoice_number": "INV-EXAMPLE-001",
"revision": "opaque-example-revision",
"store_id": 1,
"warehouse_id": 1,
"status": "partial",
"customer": {
"id": null,
"name": null
},
"due_date": null,
"created_at": "2026-10-04T12: 00: 00+00: 00",
"notes": null,
"terms": null,
"tax_treatment": "standard",
"items": [
{
"id": 1,
"item_id": 1,
"name": "Example Product",
"sku": "EXAMPLE-001",
"unit_id": null,
"warehouse_id": 1,
"quantity": "1.000",
"base_quantity": "1.000000",
"unit_price": {
"amount": "100.00",
"currency": "GHS",
"formatted": "GH\u20b5100.00"
},
"discount": {
"amount": "0.00",
"currency": "GHS",
"formatted": "GH\u20b50.00"
},
"tax": {
"amount": "0.00",
"currency": "GHS",
"formatted": "GH\u20b50.00"
},
"total": {
"amount": "100.00",
"currency": "GHS",
"formatted": "GH\u20b5100.00"
},
"serial_number": null,
"price_source": "base",
"tax_breakdown": []
}
],
"totals": {
"subtotal": {
"amount": "100.00",
"currency": "GHS",
"formatted": "GH\u20b5100.00"
},
"tax": {
"amount": "0.00",
"currency": "GHS",
"formatted": "GH\u20b50.00"
},
"discount": {
"amount": "0.00",
"currency": "GHS",
"formatted": "GH\u20b50.00"
},
"total": {
"amount": "100.00",
"currency": "GHS",
"formatted": "GH\u20b5100.00"
},
"withholding_tax": {
"amount": "0.00",
"currency": "GHS",
"formatted": "GH\u20b50.00"
},
"amount_paid": {
"amount": "40.00",
"currency": "GHS",
"formatted": "GH\u20b540.00"
},
"amount_due": {
"amount": "60.00",
"currency": "GHS",
"formatted": "GH\u20b560.00"
}
},
"withholding_tax": {
"type_id": null,
"rate": null,
"base": null,
"reference": null,
"locked": true
},
"total_credited": {
"amount": "0.00",
"currency": "GHS",
"formatted": "GH\u20b50.00"
},
"total_refunded": {
"amount": "0.00",
"currency": "GHS",
"formatted": "GH\u20b50.00"
},
"net_collected": {
"amount": "40.00",
"currency": "GHS",
"formatted": "GH\u20b540.00"
},
"payments": [
{
"id": 1,
"amount": {
"amount": "40.00",
"currency": "GHS",
"formatted": "GH\u20b540.00"
},
"method": "cash",
"date": "2026-10-05"
}
],
"payment_methods": [
{
"id": 1,
"name": "Cash",
"code": "cash",
"type": "cash",
"requires_reference": false
}
],
"actions": {
"edit": true,
"finalize": false,
"payment": true,
"cancel": false,
"pdf": true,
"email": true,
"share": true,
"create_credit": false,
"credit_history": true
},
"source_workflow": "invoice",
"source_sale": null,
"unavailable_reason": null
},
"payment": {
"id": 1,
"amount": {
"amount": "40.00",
"currency": "GHS",
"formatted": "GH\u20b540.00"
},
"method": "cash",
"date": "2026-10-05"
}
}
}
}
{
"success": false,
"code": "validation_failed",
"message": "The given data was invalid.",
"errors": {
"store_id": [
"Select an accessible active store."
]
}
}
Current actor and exact selected location, fresh capability and whole parent scope. Payment receipt additionally requires the explicit Invoice parent and retained Payment ownership. Maximum 10 linked statuses; blocking pending/queued/processing/uncertain prioritized before terminal statuses so old uncertain sends remain visible. No recipient, body or provider reference. Read-only and private no-store; never resend while blocking or unknown.
| Parameter | In | Type | Description |
|---|---|---|---|
| invoice *required | path | string | Path parameter: Invoice |
| payment *required | path | string | Path parameter: Payment |
| store_id *required | query | integer | Selected accessible active store. |
| warehouse_id | query | integer | Optional selected accessible active warehouse. |
{
"store_id": 1,
"warehouse_id": 1
}
{
"success": true,
"data": {
"items": [
{
"kind": "document_delivery",
"id": 1,
"document_kind": "invoice_payment",
"document_id": 1,
"status": "pending",
"queued_at": null,
"attempted_at": null,
"accepted_at": null,
"error_code": null
}
]
}
}
{
"success": false,
"code": "validation_failed",
"message": "The given data was invalid.",
"errors": {
"store_id": [
"Select an accessible active store."
]
}
}
Requires invoices feature, live invoices.view, existing view policy and complete parent/line scope. Reuses existing share_links_enabled and configured expiry; generates only temporary signed public.invoices.print URLs with server-owned revocable key. Existing key is reused unless regenerate=true. No financial operation or stock/payment effect. Public share is intentionally unauthenticated but signed, expiring, configured and revocable; all other PDF downloads stay authenticated. Private/no-store response. Revocation remains available when share creation is disabled. No estimate public sharing is implemented.
| Parameter | In | Type | Description |
|---|---|---|---|
| invoice *required | path | string | Path parameter: Invoice |
| store_id *required | body | integer | Selected accessible active store. |
| warehouse_id | body | integer | Optional selected accessible active warehouse. |
{
"store_id": 1,
"warehouse_id": 1
}
{
"success": true,
"data": {
"revoked": true
}
}
{
"success": false,
"code": "validation_failed",
"message": "The given data was invalid.",
"errors": {
"store_id": [
"Select an accessible active store."
]
}
}
Requires invoices feature, live invoices.view, existing view policy and complete parent/line scope. Reuses existing share_links_enabled and configured expiry; generates only temporary signed public.invoices.print URLs with server-owned revocable key. Existing key is reused unless regenerate=true. No financial operation or stock/payment effect. Public share is intentionally unauthenticated but signed, expiring, configured and revocable; all other PDF downloads stay authenticated. Private/no-store response.
| Parameter | In | Type | Description |
|---|---|---|---|
| invoice *required | path | string | Path parameter: Invoice |
| store_id *required | body | integer | Selected accessible active store. |
| warehouse_id | body | integer | Optional selected accessible active warehouse. |
| regenerate | body | boolean | Rotate the existing share key, revoking earlier links. Default false. |
{
"store_id": 1,
"warehouse_id": 1,
"regenerate": false
}
{
"success": true,
"data": {
"url": "https://example.invalid/share/invoices/1/print?expires=1791374400&key=SYNTHETIC-KEY&signature=SYNTHETIC-SIGNATURE",
"expires_at": "2026-10-04T12: 00: 00+00: 00"
}
}
{
"success": false,
"code": "validation_failed",
"message": "The given data was invalid.",
"errors": {
"store_id": [
"Select an accessible active store."
]
}
}
Durable financial reconciliation, separate from Web Order operations. Requires staff-mobile, the original actor and exact selected store/warehouse, live feature gate and action permissions. pos.sale.create requires pos.access and sales.create; estimate.create/update require estimates.view and estimates.create/edit. Cashier actions require POS enabled and cashier-sessions.open, close (including suspend/resume), or movements; only owner or view-all supervisor can hydrate the session. Results are retained indefinitely. Estimate results hydrate current authorized estimate resources; cashier results hydrate the fresh safe CashierSession DTO, without customer or sale records. Unknown keys return 404 operation_unknown; deleted or inaccessible results return 404 not_found. No mutation or offline replay. Cache-Control: private, no-store. Global contact actions pos.customer.create, pos.customer.update, invoice.customer.create, invoice.customer.update, estimate.customer.create and estimate.customer.update require the original POS/invoice/estimate capability and customers.create/edit, independent of after-sales customer history. Hydrates the minimal Customer DTO only; retained customer_id is never treated as a location-owned record. POS sale results hydrate the own-sale PosSale DTO with authoritative paid/change Money and minimal items/payments; payment references and costs are omitted. pos.draft.create/update recheck POS/owner/exact location and hydrate current intent or an erased deleted tombstone. document.sale.email rechecks the live document eligibility and returns document_delivery_id with current delivery status, never recipient/body/PDF. Delivery results are not Sale subjects and do not require sales.create. document.invoice.email, document.estimate.email and document.invoice_payment.email retain only document_delivery_id; replay and reconciliation lock/verify original document parent before delivery and recheck current feature/view/full scope. Independent delivery failures never repeat money or stock. invoice.payment returns invoice_id/payment_id and the current authorized invoice with the original retained payment. Whole parent scope and child ownership are rechecked; missing/deleted/foreign payment results fail closed. Replay after ordinary history pruning never repeats money or notifications. invoice.create/update/finalize/cancel require live invoice feature/permission and whole-document scope; result invoice_id hydrates the current invoice including snapshots and revision. Replay never repeats stock or notifications. Customer-management identities customers.customer.create/update require live customers capability and customers.create/edit, preserving global email uniqueness and minimal customer_id results. Estimate state/duplicate/conversion operations retain estimate root plus exact owned child IDs for duplicate or conversion. Replay rechecks both source and destination capabilities, permissions and whole-record scope; child ownership is verified from persisted links. Missing or inaccessible children fail closed. return.create retains Sale root and exact owned Return child; return.approve/reject/process/cancel retain return_id. Parent-first locks, current returns.view/action authority and every parent/child line scope are mandatory on replay. No automatic gateway refund or payable/approver override. Credit creation retains invoice_id plus owned credit_note_id; actions retain credit_note_id and application/refund additionally credit_note_application_id with exact parent and target/payable provenance, current target scope and view authority.
| Parameter | In | Type | Description |
|---|---|---|---|
| key *required | path | string | Permanent actor-scoped operation UUID. |
| store_id *required | query | integer | Selected accessible active store. |
| warehouse_id | query | integer | Optional selected accessible active warehouse. |
{
"store_id": 1,
"warehouse_id": 1
}
{
"success": true,
"data": {
"operation": {
"key": "00000000-0000-4000-8000-000000000001",
"replayed": true
},
"result_ids": {
"sale_id": 1
},
"resource": {
"kind": "sale",
"id": 1,
"revision": "opaque-example-revision",
"receipt_number": "RCP-20261005-0001",
"store_id": 1,
"warehouse_id": 1,
"cashier_session_id": 1,
"total": {
"amount": "12.50",
"currency": "GHS",
"formatted": "GH\u20b512.50"
},
"paid": {
"amount": "12.50",
"currency": "GHS",
"formatted": "GH\u20b512.50"
},
"change": {
"amount": "7.50",
"currency": "GHS",
"formatted": "GH\u20b57.50"
},
"items": [
{
"item_id": 1,
"quantity": "1.000",
"serial_number": null,
"price": {
"amount": "12.50",
"currency": "GHS",
"formatted": "GH\u20b512.50"
}
}
],
"payments": [
{
"payment_method_id": 1,
"code": "cash",
"amount": {
"amount": "12.50",
"currency": "GHS",
"formatted": "GH\u20b512.50"
},
"tendered": {
"amount": "20.00",
"currency": "GHS",
"formatted": "GH\u20b520.00"
},
"change": {
"amount": "7.50",
"currency": "GHS",
"formatted": "GH\u20b57.50"
}
}
],
"allowed_actions": {
"download_pdf": true,
"share": true,
"print": true,
"email": true
}
}
}
}
{
"success": false,
"code": "validation_failed",
"message": "The given data was invalid.",
"errors": {
"store_id": [
"Select an accessible active store."
]
}
}
Requires POS feature enabled and pos.access. Server CheckoutService owns pricing, promotions, taxes, discounts and unit conversion. Selected location is sellable and the cart uses one warehouse. Unknown authority fields (price, base quantity, total, change and approver IDs) never authorize values. Quotes are encrypted, expire in five minutes, and bind actor/location/purpose/cart/fresh result. POS, invoice and estimate quote tokens are not interchangeable. Gateway-required and credit methods are excluded; this workflow only records externally received money. Commitment additionally requires sales.create and an open owned cashier session. Stock and payments are atomic and exactly once. Same-key conflicting intent returns 409 operation_conflict. Own completed sale DTO is available without sales-history permission; replay and operation lookup recheck live ownership/location/permission.
| Parameter | In | Type | Description |
|---|---|---|---|
| store_id *required | body | integer | Selected accessible active store. |
| warehouse_id | body | integer | Optional selected accessible active warehouse. |
| customer_id | body | integer | Existing global customer for server pricing. |
| items *required | body | array | One to 100 lines; quantities up to three decimal places. A serialized line has quantity one and a unique serial. |
| cart_discount_type | body | string | percent or amount. |
| cart_discount_value | body | string | Nonnegative decimal string, up to two places. |
| cart_discount_reason | body | string | Maximum 500 characters. |
| tax_exempt | body | boolean | Explicit tax exemption. |
| notes | body | string | Maximum 500 characters. |
| approval_token | body | string | Five-minute encrypted manager approval bound to actor, selected location and exact cart. |
| operation_key *required | body | string | Permanent UUID for pos.sale.create. Identical committed replay precedes quote expiry and session-state validation. |
| quote_token *required | body | string | Opaque POS quote token. |
| payment_confirmed *required | body | boolean | Must confirm actual receipt of external money. |
| payments *required | body | array | Applied amounts sum exactly to amount due; cash tender may exceed applied amount, with server change. Noncash requires exact tender and reference. |
{
"store_id": 1,
"warehouse_id": 1,
"items": [
{
"id": 1,
"qty": "1.000"
}
],
"operation_key": "00000000-0000-4000-8000-000000000001",
"quote_token": "SYNTHETIC-ENCRYPTED-QUOTE",
"payment_confirmed": true,
"payments": [
{
"payment_method_id": 1,
"amount": "12.50",
"tendered": "20.00"
}
]
}
{
"success": true,
"data": {
"operation": {
"key": "00000000-0000-4000-8000-000000000001",
"replayed": false
},
"result_ids": {
"sale_id": 1
},
"resource": {
"kind": "sale",
"id": 1,
"revision": "opaque-example-revision",
"receipt_number": "RCP-20261005-0001",
"store_id": 1,
"warehouse_id": 1,
"cashier_session_id": 1,
"total": {
"amount": "12.50",
"currency": "GHS",
"formatted": "GH\u20b512.50"
},
"paid": {
"amount": "12.50",
"currency": "GHS",
"formatted": "GH\u20b512.50"
},
"change": {
"amount": "7.50",
"currency": "GHS",
"formatted": "GH\u20b57.50"
},
"items": [
{
"item_id": 1,
"quantity": "1.000",
"serial_number": null,
"price": {
"amount": "12.50",
"currency": "GHS",
"formatted": "GH\u20b512.50"
}
}
],
"payments": [
{
"payment_method_id": 1,
"code": "cash",
"amount": {
"amount": "12.50",
"currency": "GHS",
"formatted": "GH\u20b512.50"
},
"tendered": {
"amount": "20.00",
"currency": "GHS",
"formatted": "GH\u20b520.00"
},
"change": {
"amount": "7.50",
"currency": "GHS",
"formatted": "GH\u20b57.50"
}
}
],
"allowed_actions": {
"download_pdf": true,
"share": true,
"print": true,
"email": true
}
}
}
}
{
"success": false,
"code": "validation_failed",
"message": "The given data was invalid.",
"errors": {
"store_id": [
"Select an accessible active store."
]
}
}
Global customer contact DTO, never location-owned. No sales, orders, totals, purchase activity, customer groups or private notes are exposed. POS lookup requires POS feature enabled and pos.access; it does not require the after-sales customers capability.
| Parameter | In | Type | Description |
|---|---|---|---|
| q | query | string | Catalog text or barcode alias, maximum 100 characters. |
| page | query | integer | Page >= 1. |
| per_page | query | integer | Default 25; maximum 50. Invalid values return 422. |
{
"q": "example",
"page": 1,
"per_page": 1
}
{
"success": true,
"data": {
"items": [
{
"kind": "customer",
"id": 1,
"revision": "opaque-example-revision",
"name": "Example Customer",
"email": "customer@example.invalid",
"phone": null,
"address": null,
"city": null,
"state": null,
"postal_code": null,
"country": null
}
]
},
"meta": {
"current_page": 1,
"last_page": 1,
"per_page": 25,
"total": 1
}
}
{
"success": false,
"code": "validation_failed",
"message": "The given data was invalid.",
"errors": {
"store_id": [
"Select an accessible active store."
]
}
}
Requires POS feature enabled and pos.access. Server CheckoutService owns pricing, promotions, taxes, discounts and unit conversion. Selected location is sellable and the cart uses one warehouse. Unknown authority fields (price, base quantity, total, change and approver IDs) never authorize values. Quotes are encrypted, expire in five minutes, and bind actor/location/purpose/cart/fresh result. POS, invoice and estimate quote tokens are not interchangeable. Gateway-required and credit methods are excluded; this workflow only records externally received money. A wrong PIN or a manager who cannot approve answers 422; a locked manager PIN answers 423 pin_locked; too many tries from this caller answer 429.
| Parameter | In | Type | Description |
|---|---|---|---|
| store_id *required | body | integer | Selected accessible active store. |
| warehouse_id | body | integer | Optional selected accessible active warehouse. |
| customer_id | body | integer | Existing global customer for server pricing. |
| items *required | body | array | One to 100 lines; quantities up to three decimal places. A serialized line has quantity one and a unique serial. |
| cart_discount_type | body | string | percent or amount. |
| cart_discount_value | body | string | Nonnegative decimal string, up to two places. |
| cart_discount_reason | body | string | Maximum 500 characters. |
| tax_exempt | body | boolean | Explicit tax exemption. |
| notes | body | string | Maximum 500 characters. |
| approval_token | body | string | Five-minute encrypted manager approval bound to actor, selected location and exact cart. |
| body | string | Approver email when approver_id is not sent; existing scope, limits, rate limit and PIN lockout apply. | |
| approver_id | body | integer | Approver from discount-approvers; replaces email. |
| pin *required | body | string | Manager approval PIN. Never log or persist. |
{
"store_id": 1,
"warehouse_id": 1,
"items": [
{
"id": 1,
"qty": "1.000"
}
],
"email": "manager@example.invalid",
"pin": "0000"
}
{
"success": true,
"data": {
"approval_token": "SYNTHETIC-ENCRYPTED-APPROVAL",
"expires_at": "2026-10-05T12: 05: 00+00: 00"
}
}
{
"success": false,
"code": "validation_failed",
"message": "The given data was invalid.",
"errors": {
"store_id": [
"Select an accessible active store."
]
}
}
Requires POS feature enabled and pos.access. Server CheckoutService owns pricing, promotions, taxes, discounts and unit conversion. Selected location is sellable and the cart uses one warehouse. Unknown authority fields (price, base quantity, total, change and approver IDs) never authorize values. Quotes are encrypted, expire in five minutes, and bind actor/location/purpose/cart/fresh result. POS, invoice and estimate quote tokens are not interchangeable. Gateway-required and credit methods are excluded; this workflow only records externally received money. Up to 20 managers who can approve discounts at the selected location and have an unlocked PIN, excluding the caller. Send approver_id with the PIN to discount-approval; a locked manager PIN answers 423 pin_locked.
| Parameter | In | Type | Description |
|---|---|---|---|
| store_id *required | query | integer | Selected accessible active store. |
| warehouse_id | query | integer | Optional selected accessible active warehouse. |
{
"store_id": 1,
"warehouse_id": 1
}
{
"success": true,
"data": {
"approvers": [
{
"id": 2,
"name": "Example Manager",
"role": "Store Manager",
"signed_in_today": true
}
]
}
}
{
"success": false,
"code": "validation_failed",
"message": "The given data was invalid.",
"errors": {
"store_id": [
"Select an accessible active store."
]
}
}
Requires POS feature enabled and pos.access. Server CheckoutService owns pricing, promotions, taxes, discounts and unit conversion. Selected location is sellable and the cart uses one warehouse. Unknown authority fields (price, base quantity, total, change and approver IDs) never authorize values. Quotes are encrypted, expire in five minutes, and bind actor/location/purpose/cart/fresh result. POS, invoice and estimate quote tokens are not interchangeable. Gateway-required and credit methods are excluded; this workflow only records externally received money. Zero limits retain the existing unlimited-limit semantics.
| Parameter | In | Type | Description |
|---|---|---|---|
| store_id *required | query | integer | Selected accessible active store. |
| warehouse_id | query | integer | Optional selected accessible active warehouse. |
{
"store_id": 1,
"warehouse_id": 1
}
{
"success": true,
"data": {
"max_discount_percent": "5.00",
"max_discount_amount": {
"amount": "10.00",
"currency": "GHS",
"formatted": "GH\u20b510.00"
}
}
}
{
"success": false,
"code": "validation_failed",
"message": "The given data was invalid.",
"errors": {
"store_id": [
"Select an accessible active store."
]
}
}
Requires POS feature enabled and pos.access, live owner and exact selected store/warehouse. No other cashier takeover. Held intent never reserves or consumes stock. Persisted contents allow item/unit/quantity/serial and customer/tax/discount intent only; approval/PIN, alternate location, quote/pricing authority and tender fields are discarded. GET detail resumes with a fresh server quote or quote_status requires_changes and repairable unavailable references. Approvals must be obtained again. Deleted drafts erase contents and retain only a tombstone for permanent operation reconciliation.
| Parameter | In | Type | Description |
|---|---|---|---|
| store_id *required | query | integer | Selected accessible active store. |
| warehouse_id | query | integer | Optional selected accessible active warehouse. |
| page | query | integer | Page, starting at one. |
| per_page | query | integer | Page size 1 to 50, default 25. |
{
"store_id": 1,
"warehouse_id": 1,
"page": 1,
"per_page": 1
}
{
"success": true,
"data": {
"items": [
{
"kind": "pos_draft",
"id": 1,
"store_id": 1,
"warehouse_id": 1,
"revision": "opaque-example-revision",
"contents": {
"items": [
{
"id": 1,
"qty": "1.000"
}
]
},
"deleted": false
}
]
},
"meta": {
"current_page": 1,
"last_page": 1,
"per_page": 25,
"total": 1
}
}
{
"success": false,
"code": "validation_failed",
"message": "The given data was invalid.",
"errors": {
"store_id": [
"Select an accessible active store."
]
}
}
Requires POS feature enabled and pos.access, live owner and exact selected store/warehouse. No other cashier takeover. Held intent never reserves or consumes stock. Persisted contents allow item/unit/quantity/serial and customer/tax/discount intent only; approval/PIN, alternate location, quote/pricing authority and tender fields are discarded. GET detail resumes with a fresh server quote or quote_status requires_changes and repairable unavailable references. Approvals must be obtained again. Deleted drafts erase contents and retain only a tombstone for permanent operation reconciliation. Durable actions pos.draft.create and pos.draft.update return only pos_draft_id plus the current authorized draft DTO.
| Parameter | In | Type | Description |
|---|---|---|---|
| store_id *required | body | integer | Selected accessible active store. |
| warehouse_id | body | integer | Optional selected accessible active warehouse. |
| operation_key *required | body | string | Permanent UUID; same-key replay precedes revision validation. |
| contents *required | body | object | Full replacement of held cart intent. |
{
"store_id": 1,
"warehouse_id": 1,
"operation_key": "00000000-0000-4000-8000-000000000001",
"contents": {
"items": [
{
"id": 1,
"qty": "1.000"
}
]
}
}
{
"success": true,
"data": {
"operation": {
"key": "00000000-0000-4000-8000-000000000001",
"replayed": false
},
"result_ids": {
"pos_draft_id": 1
},
"resource": {
"kind": "pos_draft",
"id": 1,
"store_id": 1,
"warehouse_id": 1,
"revision": "opaque-example-revision",
"contents": {
"items": [
{
"id": 1,
"qty": "1.000"
}
]
},
"deleted": false
}
}
}
{
"success": false,
"code": "validation_failed",
"message": "The given data was invalid.",
"errors": {
"store_id": [
"Select an accessible active store."
]
}
}
Requires POS feature enabled and pos.access, live owner and exact selected store/warehouse. No other cashier takeover. Held intent never reserves or consumes stock. Persisted contents allow item/unit/quantity/serial and customer/tax/discount intent only; approval/PIN, alternate location, quote/pricing authority and tender fields are discarded. GET detail resumes with a fresh server quote or quote_status requires_changes and repairable unavailable references. Approvals must be obtained again. Deleted drafts erase contents and retain only a tombstone for permanent operation reconciliation.
| Parameter | In | Type | Description |
|---|---|---|---|
| draft *required | path | string | Path parameter: Draft |
| store_id *required | body | integer | Selected accessible active store. |
| warehouse_id | body | integer | Optional selected accessible active warehouse. |
| revision *required | body | string | Current draft revision; stale deletion returns 409 revision_conflict. No operation key required. |
{
"store_id": 1,
"warehouse_id": 1,
"revision": "opaque-example-revision"
}
{
"success": true,
"data": {
"deleted": true
}
}
{
"success": false,
"code": "validation_failed",
"message": "The given data was invalid.",
"errors": {
"store_id": [
"Select an accessible active store."
]
}
}
Requires POS feature enabled and pos.access, live owner and exact selected store/warehouse. No other cashier takeover. Held intent never reserves or consumes stock. Persisted contents allow item/unit/quantity/serial and customer/tax/discount intent only; approval/PIN, alternate location, quote/pricing authority and tender fields are discarded. GET detail resumes with a fresh server quote or quote_status requires_changes and repairable unavailable references. Approvals must be obtained again. Deleted drafts erase contents and retain only a tombstone for permanent operation reconciliation.
| Parameter | In | Type | Description |
|---|---|---|---|
| draft *required | path | string | Path parameter: Draft |
| store_id *required | query | integer | Selected accessible active store. |
| warehouse_id | query | integer | Optional selected accessible active warehouse. |
{
"store_id": 1,
"warehouse_id": 1
}
{
"success": true,
"data": {
"draft": {
"kind": "pos_draft",
"id": 1,
"store_id": 1,
"warehouse_id": 1,
"revision": "opaque-example-revision",
"contents": {
"items": [
{
"id": 1,
"qty": "1.000"
}
]
},
"deleted": false
},
"quote": {
"lines": [
{
"item_id": 1,
"quantity": "1.000",
"base_quantity": "1.000",
"sale_quantity": "1.000",
"price": {
"amount": "12.50",
"currency": "GHS",
"formatted": "GH\u20b512.50"
},
"warehouse_id": 1,
"serial_number": null,
"discount_amount": {
"amount": "0.00",
"currency": "GHS",
"formatted": "GH\u20b50.00"
},
"discount_percent": null,
"tax_rate": "0.00",
"tax_amount": {
"amount": "0.00",
"currency": "GHS",
"formatted": "GH\u20b50.00"
},
"bundle_id": null,
"pricing_rule_id": null,
"bogo_applied": false
}
],
"totals": {
"subtotal": {
"amount": "12.50",
"currency": "GHS",
"formatted": "GH\u20b512.50"
},
"subtotal_after_item_discounts": {
"amount": "12.50",
"currency": "GHS",
"formatted": "GH\u20b512.50"
},
"item_discounts": {
"amount": "0.00",
"currency": "GHS",
"formatted": "GH\u20b50.00"
},
"cart_discount_amount": {
"amount": "0.00",
"currency": "GHS",
"formatted": "GH\u20b50.00"
},
"total_discount": {
"amount": "0.00",
"currency": "GHS",
"formatted": "GH\u20b50.00"
},
"tax_amount": {
"amount": "0.00",
"currency": "GHS",
"formatted": "GH\u20b50.00"
},
"total_exclusive_tax": {
"amount": "0.00",
"currency": "GHS",
"formatted": "GH\u20b50.00"
},
"total_inclusive_tax": {
"amount": "0.00",
"currency": "GHS",
"formatted": "GH\u20b50.00"
},
"total_amount": {
"amount": "12.50",
"currency": "GHS",
"formatted": "GH\u20b512.50"
},
"pre_tax_total": {
"amount": "12.50",
"currency": "GHS",
"formatted": "GH\u20b512.50"
},
"tax_is_inclusive": false,
"tax_exempt": false
},
"applied": {
"pricing_rules": [],
"bundles": [],
"bogo_deals": []
},
"payment_methods": [
{
"id": 1,
"name": "Cash",
"code": "cash",
"type": "cash",
"requires_reference": false
}
],
"quote_token": "SYNTHETIC-ENCRYPTED-QUOTE",
"expires_at": "2026-10-05T12: 05: 00+00: 00"
},
"quote_status": "ready",
"unavailable": {
"customer": false,
"item_ids": []
},
"errors": []
}
}
{
"success": false,
"code": "validation_failed",
"message": "The given data was invalid.",
"errors": {
"store_id": [
"Select an accessible active store."
]
}
}
Requires POS feature enabled and pos.access, live owner and exact selected store/warehouse. No other cashier takeover. Held intent never reserves or consumes stock. Persisted contents allow item/unit/quantity/serial and customer/tax/discount intent only; approval/PIN, alternate location, quote/pricing authority and tender fields are discarded. GET detail resumes with a fresh server quote or quote_status requires_changes and repairable unavailable references. Approvals must be obtained again. Deleted drafts erase contents and retain only a tombstone for permanent operation reconciliation. Durable actions pos.draft.create and pos.draft.update return only pos_draft_id plus the current authorized draft DTO.
| Parameter | In | Type | Description |
|---|---|---|---|
| draft *required | path | string | Path parameter: Draft |
| store_id *required | body | integer | Selected accessible active store. |
| warehouse_id | body | integer | Optional selected accessible active warehouse. |
| operation_key *required | body | string | Permanent UUID; same-key replay precedes revision validation. |
| contents *required | body | object | Full replacement of held cart intent. |
| revision *required | body | string | Current draft revision. |
{
"store_id": 1,
"warehouse_id": 1,
"operation_key": "00000000-0000-4000-8000-000000000001",
"contents": {
"items": [
{
"id": 1,
"qty": "1.000"
}
]
},
"revision": "opaque-example-revision"
}
{
"success": true,
"data": {
"operation": {
"key": "00000000-0000-4000-8000-000000000001",
"replayed": false
},
"result_ids": {
"pos_draft_id": 1
},
"resource": {
"kind": "pos_draft",
"id": 1,
"store_id": 1,
"warehouse_id": 1,
"revision": "opaque-example-revision",
"contents": {
"items": [
{
"id": 1,
"qty": "1.000"
}
]
},
"deleted": false
}
}
}
{
"success": false,
"code": "validation_failed",
"message": "The given data was invalid.",
"errors": {
"store_id": [
"Select an accessible active store."
]
}
}
Requires POS feature enabled and pos.access. Sale commitment separately requires sales.create. Scoped to fresh accessible active sellable warehouses, including explicit assignments when warehouse_id is omitted. Inactive products and disabled services are excluded. Products outside scoped stock/serial locations return 404. Effective prices use server price books and rules; quantities subtract pending/in-transit transfer reservations. This is current availability, not a stock reservation or sale guarantee. No costs or embedded serial arrays.
| Parameter | In | Type | Description |
|---|---|---|---|
| store_id *required | query | integer | Selected accessible active store. |
| warehouse_id | query | integer | Optional selected accessible active warehouse. |
| q | query | string | Catalog text or barcode alias, maximum 100 characters. |
| category_id | query | integer | Category filter. |
| customer_id | query | integer | Global customer selected for effective server pricing. |
| page | query | integer | Page >= 1. |
| per_page | query | integer | Default 25; maximum 50. Invalid values return 422. |
{
"store_id": 1,
"warehouse_id": 1,
"q": "example",
"category_id": 1,
"customer_id": 1,
"page": 1,
"per_page": 1
}
{
"success": true,
"data": {
"items": [
{
"id": 1,
"name": "Example Product",
"sku": "EXAMPLE-001",
"barcode": null,
"barcodes": [
"EXAMPLE-ALIAS"
],
"category_id": 1,
"category_name": "Example Category",
"image_url": null,
"is_service": false,
"is_serialized": false,
"is_sold_by_weight": false,
"base_unit_id": 1,
"base_unit_code": "pc",
"price": {
"amount": "12.50",
"currency": "GHS",
"formatted": "GH\u20b512.50"
},
"sale_units": [
{
"id": 1,
"name": "Piece",
"code": "pc",
"conversion_factor": "1.000000",
"price": {
"amount": "12.50",
"currency": "GHS",
"formatted": "GH\u20b512.50"
}
}
],
"stock_quantity": "10.000000",
"sellable": true
}
]
},
"meta": {
"current_page": 1,
"last_page": 1,
"per_page": 25,
"total": 1
}
}
{
"success": false,
"code": "validation_failed",
"message": "The given data was invalid.",
"errors": {
"store_id": [
"Select an accessible active store."
]
}
}
Requires POS feature enabled and pos.access. Sale commitment separately requires sales.create. Scoped to fresh accessible active sellable warehouses, including explicit assignments when warehouse_id is omitted. Inactive products and disabled services are excluded. Products outside scoped stock/serial locations return 404. Effective prices use server price books and rules; quantities subtract pending/in-transit transfer reservations. This is current availability, not a stock reservation or sale guarantee. No costs or embedded serial arrays.
| Parameter | In | Type | Description |
|---|---|---|---|
| item *required | path | string | Path parameter: Item |
| store_id *required | query | integer | Selected accessible active store. |
| warehouse_id | query | integer | Optional selected accessible active warehouse. |
| q | query | string | Catalog text or barcode alias, maximum 100 characters. |
| category_id | query | integer | Category filter. |
| customer_id | query | integer | Global customer selected for effective server pricing. |
| page | query | integer | Page >= 1. |
| per_page | query | integer | Default 25; maximum 50. Invalid values return 422. |
{
"store_id": 1,
"warehouse_id": 1,
"q": "example",
"category_id": 1,
"customer_id": 1,
"page": 1,
"per_page": 1
}
{
"success": true,
"data": {
"product": {
"id": 1,
"name": "Example Product",
"sku": "EXAMPLE-001",
"barcode": null,
"barcodes": [
"EXAMPLE-ALIAS"
],
"category_id": 1,
"category_name": "Example Category",
"image_url": null,
"is_service": false,
"is_serialized": false,
"is_sold_by_weight": false,
"base_unit_id": 1,
"base_unit_code": "pc",
"price": {
"amount": "12.50",
"currency": "GHS",
"formatted": "GH\u20b512.50"
},
"sale_units": [
{
"id": 1,
"name": "Piece",
"code": "pc",
"conversion_factor": "1.000000",
"price": {
"amount": "12.50",
"currency": "GHS",
"formatted": "GH\u20b512.50"
}
}
],
"stock_quantity": "10.000000",
"sellable": true
}
}
}
{
"success": false,
"code": "validation_failed",
"message": "The given data was invalid.",
"errors": {
"store_id": [
"Select an accessible active store."
]
}
}
Requires POS feature enabled and pos.access. Sale commitment separately requires sales.create. Scoped to fresh accessible active sellable warehouses, including explicit assignments when warehouse_id is omitted. Inactive products and disabled services are excluded. Products outside scoped stock/serial locations return 404. Effective prices use server price books and rules; quantities subtract pending/in-transit transfer reservations. This is current availability, not a stock reservation or sale guarantee. No costs or embedded serial arrays.
| Parameter | In | Type | Description |
|---|---|---|---|
| item *required | path | string | Path parameter: Item |
| store_id *required | query | integer | Selected accessible active store. |
| warehouse_id | query | integer | Optional selected accessible active warehouse. |
| q | query | string | Catalog text or barcode alias, maximum 100 characters. |
| category_id | query | integer | Category filter. |
| customer_id | query | integer | Global customer selected for effective server pricing. |
| page | query | integer | Page >= 1. |
| per_page | query | integer | Default 25; maximum 50. Invalid values return 422. |
{
"store_id": 1,
"warehouse_id": 1,
"q": "example",
"category_id": 1,
"customer_id": 1,
"page": 1,
"per_page": 1
}
{
"success": true,
"data": {
"serials": [
{
"id": 1,
"serial_number": "EXAMPLE-SERIAL"
}
]
},
"meta": {
"current_page": 1,
"last_page": 1,
"per_page": 25,
"total": 1
}
}
{
"success": false,
"code": "validation_failed",
"message": "The given data was invalid.",
"errors": {
"store_id": [
"Select an accessible active store."
]
}
}
Requires POS feature enabled and pos.access. Server CheckoutService owns pricing, promotions, taxes, discounts and unit conversion. Selected location is sellable and the cart uses one warehouse. Unknown authority fields (price, base quantity, total, change and approver IDs) never authorize values. Quotes are encrypted, expire in five minutes, and bind actor/location/purpose/cart/fresh result. POS, invoice and estimate quote tokens are not interchangeable. Gateway-required and credit methods are excluded; this workflow only records externally received money.
| Parameter | In | Type | Description |
|---|---|---|---|
| store_id *required | body | integer | Selected accessible active store. |
| warehouse_id | body | integer | Optional selected accessible active warehouse. |
| customer_id | body | integer | Existing global customer for server pricing. |
| items *required | body | array | One to 100 lines; quantities up to three decimal places. A serialized line has quantity one and a unique serial. |
| cart_discount_type | body | string | percent or amount. |
| cart_discount_value | body | string | Nonnegative decimal string, up to two places. |
| cart_discount_reason | body | string | Maximum 500 characters. |
| tax_exempt | body | boolean | Explicit tax exemption. |
| notes | body | string | Maximum 500 characters. |
| approval_token | body | string | Five-minute encrypted manager approval bound to actor, selected location and exact cart. |
{
"store_id": 1,
"warehouse_id": 1,
"items": [
{
"id": 1,
"qty": "1.000"
}
]
}
{
"success": true,
"data": {
"lines": [
{
"item_id": 1,
"quantity": "1.000",
"base_quantity": "1.000",
"sale_quantity": "1.000",
"price": {
"amount": "12.50",
"currency": "GHS",
"formatted": "GH\u20b512.50"
},
"warehouse_id": 1,
"serial_number": null,
"discount_amount": {
"amount": "0.00",
"currency": "GHS",
"formatted": "GH\u20b50.00"
},
"discount_percent": null,
"tax_rate": "0.00",
"tax_amount": {
"amount": "0.00",
"currency": "GHS",
"formatted": "GH\u20b50.00"
},
"bundle_id": null,
"pricing_rule_id": null,
"bogo_applied": false
}
],
"totals": {
"subtotal": {
"amount": "12.50",
"currency": "GHS",
"formatted": "GH\u20b512.50"
},
"subtotal_after_item_discounts": {
"amount": "12.50",
"currency": "GHS",
"formatted": "GH\u20b512.50"
},
"item_discounts": {
"amount": "0.00",
"currency": "GHS",
"formatted": "GH\u20b50.00"
},
"cart_discount_amount": {
"amount": "0.00",
"currency": "GHS",
"formatted": "GH\u20b50.00"
},
"total_discount": {
"amount": "0.00",
"currency": "GHS",
"formatted": "GH\u20b50.00"
},
"tax_amount": {
"amount": "0.00",
"currency": "GHS",
"formatted": "GH\u20b50.00"
},
"total_exclusive_tax": {
"amount": "0.00",
"currency": "GHS",
"formatted": "GH\u20b50.00"
},
"total_inclusive_tax": {
"amount": "0.00",
"currency": "GHS",
"formatted": "GH\u20b50.00"
},
"total_amount": {
"amount": "12.50",
"currency": "GHS",
"formatted": "GH\u20b512.50"
},
"pre_tax_total": {
"amount": "12.50",
"currency": "GHS",
"formatted": "GH\u20b512.50"
},
"tax_is_inclusive": false,
"tax_exempt": false
},
"applied": {
"pricing_rules": [],
"bundles": [],
"bogo_deals": []
},
"payment_methods": [
{
"id": 1,
"name": "Cash",
"code": "cash",
"type": "cash",
"requires_reference": false
}
],
"quote_token": "SYNTHETIC-ENCRYPTED-QUOTE",
"expires_at": "2026-10-05T12: 05: 00+00: 00"
}
}
{
"success": false,
"code": "validation_failed",
"message": "The given data was invalid.",
"errors": {
"store_id": [
"Select an accessible active store."
]
}
}
Named staff-mobile token, reports feature enabled, live per-key permissions and current staff report location required. All 35 static families have typed producers. Reserved human report permissions remain restricted. Report filters are source-specific: store-only keys reject warehouse_id; all_accessible requires reports.stores and omits store_id/warehouse_id; assigned or standalone inventory selections use only warehouse_id, never owner_store. Unknown filters are rejected. Location options declare suitable report_keys and do not grant ordinary workspace access. Configured AI requires local enabled/configured readiness. No native export endpoint is implemented by this contract. Cache-Control: private, no-store. Location kinds: store, store_warehouse, assigned_warehouse, standalone_warehouse, all_accessible.
[]
{
"success": true,
"data": {
"reports": [
{
"key": "sales",
"label": "Sales",
"permission": "reports.sales",
"owner_task": 2,
"filters": {
"store_id": [
"nullable",
"integer",
"min: 1"
],
"all_accessible": [
"sometimes",
"boolean"
],
"page": [
"sometimes",
"integer",
"min: 1"
],
"per_page": [
"sometimes",
"integer",
"min: 1",
"max: 50"
],
"warehouse_id": [
"nullable",
"integer",
"min: 1"
],
"date_from": [
"sometimes",
"date_format:Y-m-d"
],
"date_to": [
"sometimes",
"date_format:Y-m-d",
"after_or_equal:date_from"
],
"comparison_mode": [
"sometimes",
"in:previous_period,same_period_last_year,same_period_last_quarter"
],
"group_by": [
"sometimes",
"in:day,week,month"
]
},
"date_default": "month",
"columns": [
{
"key": "period",
"label": "Period",
"type": "text"
},
{
"key": "count",
"label": "Count",
"type": "integer"
},
{
"key": "total",
"label": "Total",
"type": "money"
},
{
"key": "avg",
"label": "Avg",
"type": "money"
}
],
"chart_kinds": [
"bar",
"line"
],
"export_formats": [
"csv"
],
"available": true
},
{
"key": "products",
"label": "Products",
"permission": "reports.inventory",
"owner_task": 2,
"filters": {
"store_id": [
"nullable",
"integer",
"min: 1"
],
"all_accessible": [
"sometimes",
"boolean"
],
"page": [
"sometimes",
"integer",
"min: 1"
],
"per_page": [
"sometimes",
"integer",
"min: 1",
"max: 50"
],
"warehouse_id": [
"nullable",
"integer",
"min: 1"
],
"date_from": [
"sometimes",
"date_format:Y-m-d"
],
"date_to": [
"sometimes",
"date_format:Y-m-d",
"after_or_equal:date_from"
],
"comparison_mode": [
"sometimes",
"in:previous_period,same_period_last_year,same_period_last_quarter"
],
"category_id": [
"nullable",
"integer",
"min: 1"
]
},
"date_default": "month",
"columns": [
{
"key": "name",
"label": "Name",
"type": "text"
},
{
"key": "sku",
"label": "Sku",
"type": "text"
},
{
"key": "total_sold",
"label": "Total Sold",
"type": "decimal"
},
{
"key": "revenue",
"label": "Revenue",
"type": "money"
},
{
"key": "avg_price",
"label": "Avg Price",
"type": "money"
},
{
"key": "transaction_count",
"label": "Transaction Count",
"type": "integer"
}
],
"chart_kinds": [
"bar",
"line"
],
"export_formats": [
"csv"
],
"available": true
},
{
"key": "stock",
"label": "Stock",
"permission": "inventory.reports",
"owner_task": 4,
"filters": {
"store_id": [
"nullable",
"integer",
"min: 1"
],
"all_accessible": [
"sometimes",
"boolean"
],
"page": [
"sometimes",
"integer",
"min: 1"
],
"per_page": [
"sometimes",
"integer",
"min: 1",
"max: 50"
],
"warehouse_id": [
"nullable",
"integer",
"min: 1"
],
"category_id": [
"nullable",
"integer",
"min: 1"
],
"search": [
"nullable",
"string",
"max: 200"
],
"stock_status": [
"nullable",
"in:all,low,out,in_stock"
]
},
"date_default": "snapshot",
"columns": [
{
"key": "name",
"label": "Name",
"type": "text"
},
{
"key": "sku",
"label": "Sku",
"type": "text"
},
{
"key": "category.name",
"label": "Category.name",
"type": "text"
},
{
"key": "current_stock",
"label": "Current Stock",
"type": "decimal"
},
{
"key": "stock_value",
"label": "Stock Value",
"type": "money"
},
{
"key": "retail_value",
"label": "Retail Value",
"type": "money"
}
],
"chart_kinds": [
"bar",
"line"
],
"export_formats": [],
"available": true
},
{
"key": "discounts",
"label": "Discounts",
"permission": "reports.sales",
"owner_task": 2,
"filters": {
"store_id": [
"nullable",
"integer",
"min: 1"
],
"all_accessible": [
"sometimes",
"boolean"
],
"page": [
"sometimes",
"integer",
"min: 1"
],
"per_page": [
"sometimes",
"integer",
"min: 1",
"max: 50"
],
"warehouse_id": [
"nullable",
"integer",
"min: 1"
],
"date_from": [
"sometimes",
"date_format:Y-m-d"
],
"date_to": [
"sometimes",
"date_format:Y-m-d",
"after_or_equal:date_from"
],
"cashier_id": [
"nullable",
"integer",
"min: 1"
]
},
"date_default": "month",
"columns": [
{
"key": "name",
"label": "Name",
"type": "text"
},
{
"key": "qty_sold",
"label": "Qty Sold",
"type": "decimal"
},
{
"key": "gross_total",
"label": "Gross Total",
"type": "money"
},
{
"key": "discount_total",
"label": "Discount Total",
"type": "money"
},
{
"key": "discount_rate",
"label": "Discount Rate",
"type": "percent"
}
],
"chart_kinds": [
"bar",
"line"
],
"export_formats": [
"csv"
],
"available": true
},
{
"key": "closeout",
"label": "Closeout",
"permission": "reports.sales",
"owner_task": 2,
"filters": {
"store_id": [
"nullable",
"integer",
"min: 1"
],
"all_accessible": [
"sometimes",
"boolean"
],
"page": [
"sometimes",
"integer",
"min: 1"
],
"per_page": [
"sometimes",
"integer",
"min: 1",
"max: 50"
],
"warehouse_id": [
"nullable",
"integer",
"min: 1"
],
"date_from": [
"sometimes",
"date_format:Y-m-d"
],
"date_to": [
"sometimes",
"date_format:Y-m-d",
"after_or_equal:date_from"
],
"cashier_id": [
"nullable",
"integer",
"min: 1"
],
"status": [
"sometimes",
"in:closed,open,suspended"
],
"register_id": [
"nullable",
"integer",
"min: 1"
]
},
"date_default": "month",
"columns": [
{
"key": "id",
"label": "Id",
"type": "integer"
},
{
"key": "cashier",
"label": "Cashier",
"type": "text"
},
{
"key": "register",
"label": "Register",
"type": "text"
},
{
"key": "status",
"label": "Status",
"type": "text"
},
{
"key": "total_sales",
"label": "Total Sales",
"type": "money"
},
{
"key": "total_cash_sales",
"label": "Total Cash Sales",
"type": "money"
},
{
"key": "total_card_sales",
"label": "Total Card Sales",
"type": "money"
},
{
"key": "discrepancy",
"label": "Discrepancy",
"type": "money"
}
],
"chart_kinds": [
"bar",
"line"
],
"export_formats": [
"csv"
],
"available": true
},
{
"key": "cashier-detail",
"label": "Cashier Detail",
"permission": "reports.sales",
"owner_task": 5,
"filters": {
"store_id": [
"nullable",
"integer",
"min: 1"
],
"all_accessible": [
"sometimes",
"boolean"
],
"page": [
"sometimes",
"integer",
"min: 1"
],
"per_page": [
"sometimes",
"integer",
"min: 1",
"max: 50"
],
"warehouse_id": [
"nullable",
"integer",
"min: 1"
],
"date_from": [
"sometimes",
"date_format:Y-m-d"
],
"date_to": [
"sometimes",
"date_format:Y-m-d",
"after_or_equal:date_from"
],
"user_id": [
"required",
"integer",
"min: 1"
]
},
"date_default": "month",
"columns": [
{
"key": "period",
"label": "Period",
"type": "date"
},
{
"key": "transactions",
"label": "Transactions",
"type": "integer"
},
{
"key": "revenue",
"label": "Revenue",
"type": "money"
},
{
"key": "average",
"label": "Average",
"type": "money"
}
],
"chart_kinds": [
"bar",
"line"
],
"export_formats": [],
"available": true,
"filter_options": [
{
"key": "user_id",
"label": "User",
"value_type": "integer",
"options": [
{
"value": 1,
"label": "Example user"
}
],
"selected": null,
"read_only": false,
"pagination": {
"current_page": 1,
"last_page": 1,
"per_page": 25,
"total": 1
}
}
]
},
{
"key": "user-performance",
"label": "User Performance",
"permission": "users.reports.view",
"owner_task": 5,
"filters": {
"store_id": [
"nullable",
"integer",
"min: 1"
],
"all_accessible": [
"sometimes",
"boolean"
],
"page": [
"sometimes",
"integer",
"min: 1"
],
"per_page": [
"sometimes",
"integer",
"min: 1",
"max: 50"
],
"warehouse_id": [
"nullable",
"integer",
"min: 1"
],
"date_from": [
"sometimes",
"date_format:Y-m-d"
],
"date_to": [
"sometimes",
"date_format:Y-m-d",
"after_or_equal:date_from"
],
"user_id": [
"sometimes",
"integer",
"min: 1"
]
},
"date_default": "month",
"columns": [
{
"key": "period",
"label": "Period",
"type": "date"
},
{
"key": "transactions",
"label": "Transactions",
"type": "integer"
},
{
"key": "revenue",
"label": "Revenue",
"type": "money"
},
{
"key": "average",
"label": "Average",
"type": "money"
}
],
"chart_kinds": [
"bar",
"line"
],
"export_formats": [],
"available": true,
"filter_options": [
{
"key": "user_id",
"label": "User",
"value_type": "integer",
"options": [
{
"value": 1,
"label": "Example user"
}
],
"selected": null,
"read_only": false,
"pagination": {
"current_page": 1,
"last_page": 1,
"per_page": 25,
"total": 1
}
}
]
}
],
"location_options": [
{
"id": "store: 1",
"kind": "store",
"label": "Example Store",
"store_id": 1,
"store_name": "Example Store",
"warehouse_id": null,
"warehouse_name": null,
"owner_store": null,
"report_keys": [
"sales",
"products",
"stock",
"discounts",
"closeout",
"cashier-detail",
"user-performance"
]
},
{
"id": "store_warehouse: 1: 1",
"kind": "store_warehouse",
"label": "Example Warehouse",
"store_id": 1,
"store_name": "Example Store",
"warehouse_id": 1,
"warehouse_name": "Example Warehouse",
"owner_store": null,
"report_keys": [
"sales"
]
},
{
"id": "assigned_warehouse: 2",
"kind": "assigned_warehouse",
"label": "Example Warehouse",
"store_id": null,
"store_name": null,
"warehouse_id": 2,
"warehouse_name": "Example Warehouse",
"owner_store": {
"id": 1,
"name": "Example Store"
},
"report_keys": [
"sales"
]
},
{
"id": "standalone_warehouse: 3",
"kind": "standalone_warehouse",
"label": "Example Warehouse",
"store_id": null,
"store_name": null,
"warehouse_id": 3,
"warehouse_name": "Example Warehouse",
"owner_store": null,
"report_keys": [
"stock"
]
},
{
"id": "all_accessible",
"kind": "all_accessible",
"label": "All accessible locations",
"store_id": null,
"store_name": null,
"warehouse_id": null,
"warehouse_name": null,
"owner_store": null,
"report_keys": [
"sales"
]
}
]
}
}
{
"success": false,
"code": "validation_failed",
"message": "The given data was invalid.",
"errors": {
"store_id": [
"Select an accessible active store."
]
}
}
Named staff-mobile token, reports feature enabled, live per-key permissions and current staff report location required. All 35 static families have typed producers. Reserved human report permissions remain restricted. Report filters are source-specific: store-only keys reject warehouse_id; all_accessible requires reports.stores and omits store_id/warehouse_id; assigned or standalone inventory selections use only warehouse_id, never owner_store. Unknown filters are rejected. Location options declare suitable report_keys and do not grant ordinary workspace access. Configured AI requires local enabled/configured readiness. No native export endpoint is implemented by this contract. Cache-Control: private, no-store. Metadata-only response example: {"success":true,"data":{"report_key":"products","filter_options":[{"key":"category_id","label":"Category","value_type":"integer","options":[{"value":1,"label":"Example category"}],"selected":null,"read_only":false,"pagination":{"current_page":1,"last_page":1,"per_page":25,"total":1}}]}}
| Parameter | In | Type | Description |
|---|---|---|---|
| reportKey *required | path | string | Exact server catalog key. |
| store_id | query | integer | Authorized selected store; omitted for all_accessible or standalone inventory. |
| warehouse_id | query | integer | Authorized warehouse only when supported by the selected report. |
| all_accessible | query | boolean | Explicit cross-store mode; requires reports.stores and excludes selected location filters. |
| date_from | query | string | Y-m-d Africa/Accra; only date-bearing report keys. |
| date_to | query | string | Inclusive calendar day; internally exclusive next-day UTC boundary. |
| page | query | integer | Minimum 1; default 1. |
| per_page | query | integer | Default 25; maximum 50. |
| option_filter | query | string | Metadata-only mode: products/stock category_id; discounts/closeout cashier_id; closeout register_id; cashier-detail/user-performance user_id. Returns data.report_key and data.filter_options, before required subject but after all current authorization. |
| option_page | query | integer | Metadata page, default 1; clamped to last page. Options contain 25 entries per page. |
| option_search | query | string | Metadata label search, maximum 200 characters. Selected value is authorized independently of this page/search. |
{
"store_id": 1,
"warehouse_id": 1,
"all_accessible": true,
"date_from": "example",
"date_to": "example",
"page": 1,
"per_page": 1,
"option_filter": "example",
"option_page": 1,
"option_search": "example"
}
{
"success": true,
"data": {
"report_key": "sales",
"filters": {
"store_id": 1,
"date_from": "2026-10-01",
"date_to": "2026-10-04",
"page": 1,
"per_page": 25
},
"summary": [
{
"key": "total_sales",
"label": "Total",
"type": "money",
"value": {
"amount": "12.50",
"currency": "GHS",
"formatted": "GH\u20b512.50"
}
}
],
"columns": [
{
"key": "period",
"label": "Period",
"type": "text"
},
{
"key": "count",
"label": "Count",
"type": "integer"
},
{
"key": "total",
"label": "Total",
"type": "money"
},
{
"key": "avg",
"label": "Avg",
"type": "money"
}
],
"rows": [
{
"id": "2026-10-04",
"cells": {
"period": "2026-10-04",
"count": 1,
"total": {
"amount": "12.50",
"currency": "GHS",
"formatted": "GH\u20b512.50"
},
"avg": {
"amount": "12.50",
"currency": "GHS",
"formatted": "GH\u20b512.50"
}
}
}
],
"charts": [],
"meta": {
"sections": [
{
"key": "breakdown",
"label": "Sales breakdown",
"scope_label": "Selected store",
"columns": [
{
"key": "period",
"label": "Period",
"type": "text"
},
{
"key": "count",
"label": "Count",
"type": "integer"
},
{
"key": "total",
"label": "Total",
"type": "money"
},
{
"key": "avg",
"label": "Avg",
"type": "money"
}
],
"rows": [
{
"id": "2026-10-04",
"cells": {
"period": "2026-10-04",
"count": 1,
"total": {
"amount": "12.50",
"currency": "GHS",
"formatted": "GH\u20b512.50"
},
"avg": {
"amount": "12.50",
"currency": "GHS",
"formatted": "GH\u20b512.50"
}
}
}
],
"pagination": {
"current_page": 1,
"last_page": 1,
"per_page": 25,
"total": 1
}
}
],
"filter_options": []
},
"generated_at": "2026-10-04T12: 00: 00+00: 00",
"as_of": "2026-10-04T12: 00: 00+00: 00",
"export_formats": [
"csv"
]
}
}
{
"success": false,
"code": "validation_failed",
"message": "The given data was invalid.",
"errors": {
"store_id": [
"Select an accessible active store."
]
}
}
Private no-store after-sales workflow. Requires current returns.view plus the exact action permission and after-sales flag; never grants sales.view or unrelated customer history. Parent Sale, every sale line and every return line must remain in the selected authorized location. Permanent actor/action/root/location/hash UUID records retain only sale_id/return_id, survive ordinary history pruning and recheck fresh access on replay. Identical replay precedes mutable revision/state validation; changed intent is 409. Unknown outcomes require reconciliation with the original key, never a new UUID. Laravel alone owns quantities, approval, tax, refund allocation, serial disposition and stock. Default 25/max 50, scope before counts and pagination.
| Parameter | In | Type | Description |
|---|---|---|---|
| store_id *required | query | integer | Selected accessible active store. |
| warehouse_id | query | integer | Optional selected accessible active warehouse. |
| q | query | string | Literal number or stored/live customer-name search, maximum 100 characters. |
| status | query | string | pending, approved, rejected, processing, completed, cancelled. |
| date_from | query | string | Africa/Accra calendar date YYYY-MM-DD inclusive. |
| date_to | query | string | Inclusive YYYY-MM-DD, not before date_from. |
| page | query | integer | Page >= 1. |
| per_page | query | integer | Default 25, maximum 50; invalid values return 422. |
| sale_id | query | integer | Optional exact parent sale filter, current full-record scope still applies. |
{
"store_id": 1,
"warehouse_id": 1,
"q": "example",
"status": "example",
"date_from": "example",
"date_to": "example",
"page": 1,
"per_page": 1,
"sale_id": 1
}
{
"success": true,
"data": {
"items": [
{
"kind": "return",
"id": 1,
"sale_id": 1,
"number": "RTN-202610060001",
"store_id": 1,
"warehouse_id": 1,
"status": "pending",
"revision": "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa",
"return_type": "partial",
"customer": {
"id": 1,
"name": "Stored Customer"
},
"subtotal": {
"amount": "12.50",
"currency": "GHS",
"formatted": "GH\u20b512.50"
},
"tax_refund": {
"amount": "0.00",
"currency": "GHS",
"formatted": "GH\u20b50.00"
},
"total_refund": {
"amount": "12.50",
"currency": "GHS",
"formatted": "GH\u20b512.50"
},
"refunded": {
"amount": "0.00",
"currency": "GHS",
"formatted": "GH\u20b50.00"
},
"requires_approval": false,
"approved_at": null,
"completed_at": null,
"created_at": "2026-10-04T12: 00: 00+00: 00",
"reason_id": 1,
"reason_notes": null,
"rejection_reason": null,
"items": [
{
"id": 1,
"sale_item_id": 1,
"name": "Stored Product",
"sku": "STORED-SKU",
"warehouse_id": 1,
"quantity": "1.000",
"base_quantity": "1.000000",
"unit": {
"id": 1,
"name": "pc"
},
"condition": "resellable",
"restock": true,
"restocked": false,
"serial_number": null,
"refund": {
"amount": "12.50",
"currency": "GHS",
"formatted": "GH\u20b512.50"
},
"tax_refund": {
"amount": "0.00",
"currency": "GHS",
"formatted": "GH\u20b50.00"
}
}
],
"refund_plan": {
"requires_gateway": false,
"available": true,
"allocations": [
{
"payment_method_id": 1,
"name": "Cash",
"amount": {
"amount": "12.50",
"currency": "GHS",
"formatted": "GH\u20b512.50"
}
}
]
},
"refunds": [],
"allowed_actions": {
"approve": true,
"reject": true,
"cancel": true,
"process": true
}
}
]
},
"meta": {
"current_page": 1,
"last_page": 1,
"per_page": 25,
"total": 1
}
}
{
"success": false,
"code": "validation_failed",
"message": "The given data was invalid.",
"errors": {
"store_id": [
"Select an accessible active store."
]
}
}
Private no-store after-sales workflow. Requires current returns.view plus the exact action permission and after-sales flag; never grants sales.view or unrelated customer history. Parent Sale, every sale line and every return line must remain in the selected authorized location. Permanent actor/action/root/location/hash UUID records retain only sale_id/return_id, survive ordinary history pruning and recheck fresh access on replay. Identical replay precedes mutable revision/state validation; changed intent is 409. Unknown outcomes require reconciliation with the original key, never a new UUID. Laravel alone owns quantities, approval, tax, refund allocation, serial disposition and stock. refund_plan allocations are remaining externally refundable original-method totals, not gateway instructions. Stored parent display snapshots survive deleted relations; no contact details or costs.
| Parameter | In | Type | Description |
|---|---|---|---|
| return *required | path | string | Path parameter: Return |
| store_id *required | query | integer | Selected accessible active store. |
| warehouse_id | query | integer | Optional selected accessible active warehouse. |
{
"store_id": 1,
"warehouse_id": 1
}
{
"success": true,
"data": {
"kind": "return",
"id": 1,
"sale_id": 1,
"number": "RTN-202610060001",
"store_id": 1,
"warehouse_id": 1,
"status": "pending",
"revision": "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa",
"return_type": "partial",
"customer": {
"id": 1,
"name": "Stored Customer"
},
"subtotal": {
"amount": "12.50",
"currency": "GHS",
"formatted": "GH\u20b512.50"
},
"tax_refund": {
"amount": "0.00",
"currency": "GHS",
"formatted": "GH\u20b50.00"
},
"total_refund": {
"amount": "12.50",
"currency": "GHS",
"formatted": "GH\u20b512.50"
},
"refunded": {
"amount": "0.00",
"currency": "GHS",
"formatted": "GH\u20b50.00"
},
"requires_approval": false,
"approved_at": null,
"completed_at": null,
"created_at": "2026-10-04T12: 00: 00+00: 00",
"reason_id": 1,
"reason_notes": null,
"rejection_reason": null,
"items": [
{
"id": 1,
"sale_item_id": 1,
"name": "Stored Product",
"sku": "STORED-SKU",
"warehouse_id": 1,
"quantity": "1.000",
"base_quantity": "1.000000",
"unit": {
"id": 1,
"name": "pc"
},
"condition": "resellable",
"restock": true,
"restocked": false,
"serial_number": null,
"refund": {
"amount": "12.50",
"currency": "GHS",
"formatted": "GH\u20b512.50"
},
"tax_refund": {
"amount": "0.00",
"currency": "GHS",
"formatted": "GH\u20b50.00"
}
}
],
"refund_plan": {
"requires_gateway": false,
"available": true,
"allocations": [
{
"payment_method_id": 1,
"name": "Cash",
"amount": {
"amount": "12.50",
"currency": "GHS",
"formatted": "GH\u20b512.50"
}
}
]
},
"refunds": [],
"allowed_actions": {
"approve": true,
"reject": true,
"cancel": true,
"process": true
}
}
}
{
"success": false,
"code": "validation_failed",
"message": "The given data was invalid.",
"errors": {
"store_id": [
"Select an accessible active store."
]
}
}
Private no-store after-sales workflow. Requires current returns.view plus the exact action permission and after-sales flag; never grants sales.view or unrelated customer history. Parent Sale, every sale line and every return line must remain in the selected authorized location. Permanent actor/action/root/location/hash UUID records retain only sale_id/return_id, survive ordinary history pruning and recheck fresh access on replay. Identical replay precedes mutable revision/state validation; changed intent is 409. Unknown outcomes require reconciliation with the original key, never a new UUID. Laravel alone owns quantities, approval, tax, refund allocation, serial disposition and stock. Manual processing atomically records negative refunds against original payment parents, original quantities, inventory batches/movements and completed state once. Existing web manual processing delegates here; gateway processing remains separate. Any required gateway part, including mixed tender, fails staff_refund_requires_gateway before stock, with no cash remapping. Current configured approval and manual method availability are enforced; damaged/defective/opened items never enter sellable stock. Completed returns cannot process under a new UUID; legacy Processing recognizes already-recorded original-parent allocations. Notifications run after the outer commit and routine delivery failure cannot repeat money or stock. Unknown fields are rejected.
| Parameter | In | Type | Description |
|---|---|---|---|
| return *required | path | string | Path parameter: Return |
| store_id *required | body | integer | Selected accessible active store. |
| warehouse_id | body | integer | Optional selected accessible active warehouse. |
| operation_key *required | body | string | Permanent UUID; same-key authorized replay precedes current revision checks. |
| revision *required | body | string | Current 64-character parent Sale revision for creation, Return revision otherwise. |
{
"store_id": 1,
"warehouse_id": 1,
"operation_key": "00000000-0000-4000-8000-000000000001",
"revision": "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"
}
{
"success": true,
"data": {
"operation": {
"key": "00000000-0000-4000-8000-000000000001",
"replayed": false
},
"result_ids": {
"return_id": 1
},
"resource": {
"kind": "return",
"id": 1,
"sale_id": 1,
"number": "RTN-202610060001",
"store_id": 1,
"warehouse_id": 1,
"status": "approved",
"revision": "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa",
"return_type": "partial",
"customer": {
"id": 1,
"name": "Stored Customer"
},
"subtotal": {
"amount": "12.50",
"currency": "GHS",
"formatted": "GH\u20b512.50"
},
"tax_refund": {
"amount": "0.00",
"currency": "GHS",
"formatted": "GH\u20b50.00"
},
"total_refund": {
"amount": "12.50",
"currency": "GHS",
"formatted": "GH\u20b512.50"
},
"refunded": {
"amount": "0.00",
"currency": "GHS",
"formatted": "GH\u20b50.00"
},
"requires_approval": false,
"approved_at": null,
"completed_at": null,
"created_at": "2026-10-04T12: 00: 00+00: 00",
"reason_id": 1,
"reason_notes": null,
"rejection_reason": null,
"items": [
{
"id": 1,
"sale_item_id": 1,
"name": "Stored Product",
"sku": "STORED-SKU",
"warehouse_id": 1,
"quantity": "1.000",
"base_quantity": "1.000000",
"unit": {
"id": 1,
"name": "pc"
},
"condition": "resellable",
"restock": true,
"restocked": false,
"serial_number": null,
"refund": {
"amount": "12.50",
"currency": "GHS",
"formatted": "GH\u20b512.50"
},
"tax_refund": {
"amount": "0.00",
"currency": "GHS",
"formatted": "GH\u20b50.00"
}
}
],
"refund_plan": {
"requires_gateway": false,
"available": true,
"allocations": [
{
"payment_method_id": 1,
"name": "Cash",
"amount": {
"amount": "12.50",
"currency": "GHS",
"formatted": "GH\u20b512.50"
}
}
]
},
"refunds": [],
"allowed_actions": {
"approve": false,
"reject": false,
"cancel": true,
"process": true
}
}
}
}
{
"success": false,
"code": "validation_failed",
"message": "The given data was invalid.",
"errors": {
"store_id": [
"Select an accessible active store."
]
}
}
Private no-store after-sales workflow. Requires current returns.view plus the exact action permission and after-sales flag; never grants sales.view or unrelated customer history. Parent Sale, every sale line and every return line must remain in the selected authorized location. Permanent actor/action/root/location/hash UUID records retain only sale_id/return_id, survive ordinary history pruning and recheck fresh access on replay. Identical replay precedes mutable revision/state validation; changed intent is 409. Unknown outcomes require reconciliation with the original key, never a new UUID. Laravel alone owns quantities, approval, tax, refund allocation, serial disposition and stock. Manual processing atomically records negative refunds against original payment parents, original quantities, inventory batches/movements and completed state once. Existing web manual processing delegates here; gateway processing remains separate. Any required gateway part, including mixed tender, fails staff_refund_requires_gateway before stock, with no cash remapping. Current configured approval and manual method availability are enforced; damaged/defective/opened items never enter sellable stock. Completed returns cannot process under a new UUID; legacy Processing recognizes already-recorded original-parent allocations. Notifications run after the outer commit and routine delivery failure cannot repeat money or stock. Unknown fields are rejected.
| Parameter | In | Type | Description |
|---|---|---|---|
| return *required | path | string | Path parameter: Return |
| store_id *required | body | integer | Selected accessible active store. |
| warehouse_id | body | integer | Optional selected accessible active warehouse. |
| operation_key *required | body | string | Permanent UUID; same-key authorized replay precedes current revision checks. |
| revision *required | body | string | Current 64-character parent Sale revision for creation, Return revision otherwise. |
{
"store_id": 1,
"warehouse_id": 1,
"operation_key": "00000000-0000-4000-8000-000000000001",
"revision": "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"
}
{
"success": true,
"data": {
"operation": {
"key": "00000000-0000-4000-8000-000000000001",
"replayed": false
},
"result_ids": {
"return_id": 1
},
"resource": {
"kind": "return",
"id": 1,
"sale_id": 1,
"number": "RTN-202610060001",
"store_id": 1,
"warehouse_id": 1,
"status": "cancelled",
"revision": "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa",
"return_type": "partial",
"customer": {
"id": 1,
"name": "Stored Customer"
},
"subtotal": {
"amount": "12.50",
"currency": "GHS",
"formatted": "GH\u20b512.50"
},
"tax_refund": {
"amount": "0.00",
"currency": "GHS",
"formatted": "GH\u20b50.00"
},
"total_refund": {
"amount": "12.50",
"currency": "GHS",
"formatted": "GH\u20b512.50"
},
"refunded": {
"amount": "0.00",
"currency": "GHS",
"formatted": "GH\u20b50.00"
},
"requires_approval": false,
"approved_at": null,
"completed_at": null,
"created_at": "2026-10-04T12: 00: 00+00: 00",
"reason_id": 1,
"reason_notes": null,
"rejection_reason": null,
"items": [
{
"id": 1,
"sale_item_id": 1,
"name": "Stored Product",
"sku": "STORED-SKU",
"warehouse_id": 1,
"quantity": "1.000",
"base_quantity": "1.000000",
"unit": {
"id": 1,
"name": "pc"
},
"condition": "resellable",
"restock": true,
"restocked": false,
"serial_number": null,
"refund": {
"amount": "12.50",
"currency": "GHS",
"formatted": "GH\u20b512.50"
},
"tax_refund": {
"amount": "0.00",
"currency": "GHS",
"formatted": "GH\u20b50.00"
}
}
],
"refund_plan": {
"requires_gateway": false,
"available": true,
"allocations": [
{
"payment_method_id": 1,
"name": "Cash",
"amount": {
"amount": "12.50",
"currency": "GHS",
"formatted": "GH\u20b512.50"
}
}
]
},
"refunds": [],
"allowed_actions": {
"approve": false,
"reject": false,
"cancel": false,
"process": false
}
}
}
}
{
"success": false,
"code": "validation_failed",
"message": "The given data was invalid.",
"errors": {
"store_id": [
"Select an accessible active store."
]
}
}
Private no-store after-sales workflow. Requires current returns.view plus the exact action permission and after-sales flag; never grants sales.view or unrelated customer history. Parent Sale, every sale line and every return line must remain in the selected authorized location. Permanent actor/action/root/location/hash UUID records retain only sale_id/return_id, survive ordinary history pruning and recheck fresh access on replay. Identical replay precedes mutable revision/state validation; changed intent is 409. Unknown outcomes require reconciliation with the original key, never a new UUID. Laravel alone owns quantities, approval, tax, refund allocation, serial disposition and stock. Manual processing atomically records negative refunds against original payment parents, original quantities, inventory batches/movements and completed state once. Existing web manual processing delegates here; gateway processing remains separate. Any required gateway part, including mixed tender, fails staff_refund_requires_gateway before stock, with no cash remapping. Current configured approval and manual method availability are enforced; damaged/defective/opened items never enter sellable stock. Completed returns cannot process under a new UUID; legacy Processing recognizes already-recorded original-parent allocations. Notifications run after the outer commit and routine delivery failure cannot repeat money or stock. Unknown fields are rejected. Requires a single-use X-Staff-Step-Up confirmation from auth/step-up when step-up enforcement and device security are on; a missing confirmation then returns 428 step_up_required, and an invalid one always does.
| Parameter | In | Type | Description |
|---|---|---|---|
| return *required | path | string | Path parameter: Return |
| store_id *required | body | integer | Selected accessible active store. |
| warehouse_id | body | integer | Optional selected accessible active warehouse. |
| operation_key *required | body | string | Permanent UUID; same-key authorized replay precedes current revision checks. |
| revision *required | body | string | Current 64-character parent Sale revision for creation, Return revision otherwise. |
| refund_confirmed *required | body | boolean | Explicit actual external refund confirmation. |
| refunds *required | body | array | Exact remaining original-method totals from current refund_plan; one entry per method, optional external reference. Empty only when legacy Processing already recorded every allocation. |
{
"store_id": 1,
"warehouse_id": 1,
"operation_key": "00000000-0000-4000-8000-000000000001",
"revision": "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa",
"refund_confirmed": true,
"refunds": [
{
"payment_method_id": 1,
"amount": "12.50",
"reference": "Synthetic external refund"
}
]
}
{
"success": true,
"data": {
"operation": {
"key": "00000000-0000-4000-8000-000000000001",
"replayed": false
},
"result_ids": {
"return_id": 1
},
"resource": {
"kind": "return",
"id": 1,
"sale_id": 1,
"number": "RTN-202610060001",
"store_id": 1,
"warehouse_id": 1,
"status": "completed",
"revision": "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa",
"return_type": "partial",
"customer": {
"id": 1,
"name": "Stored Customer"
},
"subtotal": {
"amount": "12.50",
"currency": "GHS",
"formatted": "GH\u20b512.50"
},
"tax_refund": {
"amount": "0.00",
"currency": "GHS",
"formatted": "GH\u20b50.00"
},
"total_refund": {
"amount": "12.50",
"currency": "GHS",
"formatted": "GH\u20b512.50"
},
"refunded": {
"amount": "12.50",
"currency": "GHS",
"formatted": "GH\u20b512.50"
},
"requires_approval": false,
"approved_at": null,
"completed_at": "2026-10-04T12: 00: 00+00: 00",
"created_at": "2026-10-04T12: 00: 00+00: 00",
"reason_id": 1,
"reason_notes": null,
"rejection_reason": null,
"items": [
{
"id": 1,
"sale_item_id": 1,
"name": "Stored Product",
"sku": "STORED-SKU",
"warehouse_id": 1,
"quantity": "1.000",
"base_quantity": "1.000000",
"unit": {
"id": 1,
"name": "pc"
},
"condition": "resellable",
"restock": true,
"restocked": true,
"serial_number": null,
"refund": {
"amount": "12.50",
"currency": "GHS",
"formatted": "GH\u20b512.50"
},
"tax_refund": {
"amount": "0.00",
"currency": "GHS",
"formatted": "GH\u20b50.00"
}
}
],
"refund_plan": {
"requires_gateway": false,
"available": true,
"allocations": []
},
"refunds": [
{
"id": 1,
"payment_method_id": 1,
"amount": {
"amount": "12.50",
"currency": "GHS",
"formatted": "GH\u20b512.50"
},
"status": "completed",
"created_at": "2026-10-04T12: 00: 00+00: 00"
}
],
"allowed_actions": {
"approve": false,
"reject": false,
"cancel": false,
"process": false
}
}
}
}
{
"success": false,
"code": "validation_failed",
"message": "The given data was invalid.",
"errors": {
"store_id": [
"Select an accessible active store."
]
}
}
Private no-store after-sales workflow. Requires current returns.view plus the exact action permission and after-sales flag; never grants sales.view or unrelated customer history. Parent Sale, every sale line and every return line must remain in the selected authorized location. Permanent actor/action/root/location/hash UUID records retain only sale_id/return_id, survive ordinary history pruning and recheck fresh access on replay. Identical replay precedes mutable revision/state validation; changed intent is 409. Unknown outcomes require reconciliation with the original key, never a new UUID. Laravel alone owns quantities, approval, tax, refund allocation, serial disposition and stock. Manual processing atomically records negative refunds against original payment parents, original quantities, inventory batches/movements and completed state once. Existing web manual processing delegates here; gateway processing remains separate. Any required gateway part, including mixed tender, fails staff_refund_requires_gateway before stock, with no cash remapping. Current configured approval and manual method availability are enforced; damaged/defective/opened items never enter sellable stock. Completed returns cannot process under a new UUID; legacy Processing recognizes already-recorded original-parent allocations. Notifications run after the outer commit and routine delivery failure cannot repeat money or stock. Unknown fields are rejected.
| Parameter | In | Type | Description |
|---|---|---|---|
| return *required | path | string | Path parameter: Return |
| store_id *required | body | integer | Selected accessible active store. |
| warehouse_id | body | integer | Optional selected accessible active warehouse. |
| operation_key *required | body | string | Permanent UUID; same-key authorized replay precedes current revision checks. |
| revision *required | body | string | Current 64-character parent Sale revision for creation, Return revision otherwise. |
| reason *required | body | string | Rejection reason, maximum 2000 characters. |
{
"store_id": 1,
"warehouse_id": 1,
"operation_key": "00000000-0000-4000-8000-000000000001",
"revision": "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa",
"reason": "Return not eligible"
}
{
"success": true,
"data": {
"operation": {
"key": "00000000-0000-4000-8000-000000000001",
"replayed": false
},
"result_ids": {
"return_id": 1
},
"resource": {
"kind": "return",
"id": 1,
"sale_id": 1,
"number": "RTN-202610060001",
"store_id": 1,
"warehouse_id": 1,
"status": "rejected",
"revision": "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa",
"return_type": "partial",
"customer": {
"id": 1,
"name": "Stored Customer"
},
"subtotal": {
"amount": "12.50",
"currency": "GHS",
"formatted": "GH\u20b512.50"
},
"tax_refund": {
"amount": "0.00",
"currency": "GHS",
"formatted": "GH\u20b50.00"
},
"total_refund": {
"amount": "12.50",
"currency": "GHS",
"formatted": "GH\u20b512.50"
},
"refunded": {
"amount": "0.00",
"currency": "GHS",
"formatted": "GH\u20b50.00"
},
"requires_approval": false,
"approved_at": null,
"completed_at": null,
"created_at": "2026-10-04T12: 00: 00+00: 00",
"reason_id": 1,
"reason_notes": null,
"rejection_reason": null,
"items": [
{
"id": 1,
"sale_item_id": 1,
"name": "Stored Product",
"sku": "STORED-SKU",
"warehouse_id": 1,
"quantity": "1.000",
"base_quantity": "1.000000",
"unit": {
"id": 1,
"name": "pc"
},
"condition": "resellable",
"restock": true,
"restocked": false,
"serial_number": null,
"refund": {
"amount": "12.50",
"currency": "GHS",
"formatted": "GH\u20b512.50"
},
"tax_refund": {
"amount": "0.00",
"currency": "GHS",
"formatted": "GH\u20b50.00"
}
}
],
"refund_plan": {
"requires_gateway": false,
"available": true,
"allocations": [
{
"payment_method_id": 1,
"name": "Cash",
"amount": {
"amount": "12.50",
"currency": "GHS",
"formatted": "GH\u20b512.50"
}
}
]
},
"refunds": [],
"allowed_actions": {
"approve": false,
"reject": false,
"cancel": false,
"process": false
}
}
}
}
{
"success": false,
"code": "validation_failed",
"message": "The given data was invalid.",
"errors": {
"store_id": [
"Select an accessible active store."
]
}
}
Private no-store scoped history; after-sales feature and full sales.view required (existing super-admin-only restriction remains). No history is granted by POS creation or estimates.convert. Complete root and every line are scoped before database pagination/counts. Web Order sales additionally require live web-orders.view and exact linked-order scope. Standalone issued invoices require invoice feature and invoices.view; only sent/partial/paid/overdue, excluding sale_id-linked duplicate income. Sources POS, Invoice and Web Order remain distinct. Links are native kind/id, not public URLs; no unscoped contact fields, costs or aggregates. Stored new-record display snapshots survive renamed/deleted relations; legacy missing snapshots use neutral fallback.
| Parameter | In | Type | Description |
|---|---|---|---|
| store_id *required | query | integer | Selected accessible active store. |
| warehouse_id | query | integer | Optional selected accessible active warehouse. |
| q | query | string | Literal number or stored/live customer-name search, maximum 100 characters. |
| source | query | string | all (default), pos, invoice, web_order. |
| status | query | string | pending, completed, refunded, voided, sent, partial, paid, overdue. |
| customer_id | query | integer | Optional global customer filter; no contact authority. |
| date_from | query | string | Africa/Accra calendar date YYYY-MM-DD inclusive. |
| date_to | query | string | Inclusive YYYY-MM-DD, not before date_from. |
| page | query | integer | Page >= 1. |
| per_page | query | integer | Default 25, maximum 50; invalid values return 422. |
{
"store_id": 1,
"warehouse_id": 1,
"q": "example",
"source": "example",
"status": "example",
"customer_id": 1,
"date_from": "example",
"date_to": "example",
"page": 1,
"per_page": 1
}
{
"success": true,
"data": {
"items": [
{
"kind": "sale",
"id": 1,
"source": "pos",
"source_label": "POS",
"number": "RCP-EXAMPLE-001",
"status": "completed",
"store_id": 1,
"warehouse_id": 1,
"created_at": "2026-10-04T12: 00: 00+00: 00",
"customer": {
"id": 1,
"name": "Stored Customer",
"available": true
},
"total": {
"amount": "12.50",
"currency": "GHS",
"formatted": "GH\u20b512.50"
},
"link": {
"kind": "sale",
"id": 1
}
},
{
"kind": "invoice",
"id": 1,
"source": "invoice",
"source_label": "Invoice",
"number": "INV-EXAMPLE-001",
"status": "sent",
"store_id": 1,
"warehouse_id": 1,
"created_at": "2026-10-04T12: 00: 00+00: 00",
"customer": {
"id": 1,
"name": "Stored Customer",
"available": true
},
"total": {
"amount": "12.50",
"currency": "GHS",
"formatted": "GH\u20b512.50"
},
"link": {
"kind": "invoice",
"id": 1
}
}
]
},
"meta": {
"current_page": 1,
"last_page": 1,
"per_page": 25,
"total": 2
}
}
{
"success": false,
"code": "validation_failed",
"message": "The given data was invalid.",
"errors": {
"store_id": [
"Select an accessible active store."
]
}
}
Private no-store scoped history; after-sales feature and full sales.view required (existing super-admin-only restriction remains). No history is granted by POS creation or estimates.convert. Complete root and every line are scoped before database pagination/counts. Web Order sales additionally require live web-orders.view and exact linked-order scope. Standalone issued invoices require invoice feature and invoices.view; only sent/partial/paid/overdue, excluding sale_id-linked duplicate income. Sources POS, Invoice and Web Order remain distinct. Links are native kind/id, not public URLs; no unscoped contact fields, costs or aggregates. Stored new-record display snapshots survive renamed/deleted relations; legacy missing snapshots use neutral fallback. Exact persisted Sale child only, including historical converted estimates; never infer a receipt from estimate values. Related invoices have fresh source permission and full child scope. Original protected POS own-receipt and Web Order PDFs retain their current authorization; document actions are server-provided and may be false for a history reader.
| Parameter | In | Type | Description |
|---|---|---|---|
| sale *required | path | string | Path parameter: Sale |
| store_id *required | query | integer | Selected accessible active store. |
| warehouse_id | query | integer | Optional selected accessible active warehouse. |
{
"store_id": 1,
"warehouse_id": 1
}
{
"success": true,
"data": {
"kind": "sale",
"id": 1,
"source": "pos",
"source_label": "POS",
"number": "RCP-EXAMPLE-001",
"status": "completed",
"store_id": 1,
"warehouse_id": 1,
"created_at": "2026-10-04T12: 00: 00+00: 00",
"customer": {
"id": 1,
"name": "Stored Customer",
"available": true
},
"total": {
"amount": "12.50",
"currency": "GHS",
"formatted": "GH\u20b512.50"
},
"link": {
"kind": "sale",
"id": 1
},
"revision": "opaque-example-revision",
"receipt_number": "RCP-EXAMPLE-001",
"items": [
{
"id": 1,
"item_id": 1,
"name": "Stored Product",
"sku": "STORED-SKU",
"available": true,
"unit_id": 1,
"unit": "pc",
"warehouse_id": 1,
"quantity": "1.000",
"base_quantity": "1.000000",
"serial_number": null,
"price": {
"amount": "12.50",
"currency": "GHS",
"formatted": "GH\u20b512.50"
},
"discount": {
"amount": "0.00",
"currency": "GHS",
"formatted": "GH\u20b50.00"
},
"tax": {
"amount": "0.00",
"currency": "GHS",
"formatted": "GH\u20b50.00"
}
}
],
"payments": [
{
"id": 1,
"payment_method_id": 1,
"code": "cash",
"amount": {
"amount": "12.50",
"currency": "GHS",
"formatted": "GH\u20b512.50"
},
"tendered": {
"amount": "12.50",
"currency": "GHS",
"formatted": "GH\u20b512.50"
},
"change": {
"amount": "0.00",
"currency": "GHS",
"formatted": "GH\u20b50.00"
}
}
],
"related": [
{
"kind": "invoice",
"id": 1
}
],
"allowed_actions": {
"download_pdf": false,
"share": false,
"print": false,
"email": false,
"sale_void": false,
"create_return": false,
"return_history": true
},
"receipt": null
}
}
{
"success": false,
"code": "validation_failed",
"message": "The given data was invalid.",
"errors": {
"store_id": [
"Select an accessible active store."
]
}
}
Private finalized web-order receipt. Requires a seven-day staff-mobile token, live sales.view, completed sale, paid linked order, and selected-location access to every sale and linked-order line. Binary application/pdf attachment with sanitized filename and Cache-Control: private, no-store; never a public token or URL. Standalone POS receipt routes are unchanged.
| Parameter | In | Type | Description |
|---|---|---|---|
| sale *required | path | string | Path parameter: Sale |
| store_id *required | query | integer | Selected accessible active store. |
| warehouse_id | query | integer | Optional selected accessible active warehouse. |
{
"store_id": 1,
"warehouse_id": 1
}
"%PDF-1.7 (synthetic binary example)"
{
"success": false,
"code": "validation_failed",
"message": "The given data was invalid.",
"errors": {
"store_id": [
"Select an accessible active store."
]
}
}
Private no-store after-sales workflow. Requires current returns.view plus the exact action permission and after-sales flag; never grants sales.view or unrelated customer history. Parent Sale, every sale line and every return line must remain in the selected authorized location. Permanent actor/action/root/location/hash UUID records retain only sale_id/return_id, survive ordinary history pruning and recheck fresh access on replay. Identical replay precedes mutable revision/state validation; changed intent is 409. Unknown outcomes require reconciliation with the original key, never a new UUID. Laravel alone owns quantities, approval, tax, refund allocation, serial disposition and stock. Existing completed-sale return window and partial-return rules; distinct serialized lines require quantity/base one. Nonserialized intent accepts at most three decimal places. No stock reservation or refund starts here.
| Parameter | In | Type | Description |
|---|---|---|---|
| sale *required | path | string | Path parameter: Sale |
| store_id *required | query | integer | Selected accessible active store. |
| warehouse_id | query | integer | Optional selected accessible active warehouse. |
{
"store_id": 1,
"warehouse_id": 1
}
{
"success": true,
"data": {
"sale_id": 1,
"revision": "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa",
"eligible": true,
"reasons": [],
"items": [
{
"sale_item_id": 1,
"name": "Stored Product",
"sku": "STORED-SKU",
"available": true,
"warehouse_id": 1,
"unit": {
"id": 1,
"name": "pc"
},
"quantity": "2.000",
"returned_quantity": "0.000",
"returnable_quantity": "2.000",
"serial_number": null,
"serialized": false
}
],
"return_reasons": [
{
"id": 1,
"name": "Customer changed mind",
"allow_restock": true
}
],
"conditions": [
"resellable",
"damaged",
"defective",
"opened"
],
"requires_gateway": false
}
}
{
"success": false,
"code": "validation_failed",
"message": "The given data was invalid.",
"errors": {
"store_id": [
"Select an accessible active store."
]
}
}
Private no-store after-sales workflow. Requires current returns.view plus the exact action permission and after-sales flag; never grants sales.view or unrelated customer history. Parent Sale, every sale line and every return line must remain in the selected authorized location. Permanent actor/action/root/location/hash UUID records retain only sale_id/return_id, survive ordinary history pruning and recheck fresh access on replay. Identical replay precedes mutable revision/state validation; changed intent is 409. Unknown outcomes require reconciliation with the original key, never a new UUID. Laravel alone owns quantities, approval, tax, refund allocation, serial disposition and stock. Manual processing atomically records negative refunds against original payment parents, original quantities, inventory batches/movements and completed state once. Existing web manual processing delegates here; gateway processing remains separate. Any required gateway part, including mixed tender, fails staff_refund_requires_gateway before stock, with no cash remapping. Current configured approval and manual method availability are enforced; damaged/defective/opened items never enter sellable stock. Completed returns cannot process under a new UUID; legacy Processing recognizes already-recorded original-parent allocations. Notifications run after the outer commit and routine delivery failure cannot repeat money or stock. Unknown fields are rejected.
| Parameter | In | Type | Description |
|---|---|---|---|
| sale *required | path | string | Path parameter: Sale |
| store_id *required | body | integer | Selected accessible active store. |
| warehouse_id | body | integer | Optional selected accessible active warehouse. |
| operation_key *required | body | string | Permanent UUID; same-key authorized replay precedes current revision checks. |
| revision *required | body | string | Current 64-character parent Sale revision for creation, Return revision otherwise. |
| items *required | body | array | Distinct original sale lines; serial, payable, actor, approver and financial fields are server-owned. |
| reason_id | body | integer | Existing active reason. |
| reason_notes | body | string | Maximum 2000 characters. |
{
"store_id": 1,
"warehouse_id": 1,
"operation_key": "00000000-0000-4000-8000-000000000001",
"revision": "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa",
"items": [
{
"sale_item_id": 1,
"quantity": "1.000",
"condition": "resellable",
"restock": true
}
]
}
{
"success": true,
"data": {
"operation": {
"key": "00000000-0000-4000-8000-000000000001",
"replayed": false
},
"result_ids": {
"sale_id": 1,
"return_id": 1
},
"resource": {
"kind": "return",
"id": 1,
"sale_id": 1,
"number": "RTN-202610060001",
"store_id": 1,
"warehouse_id": 1,
"status": "pending",
"revision": "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa",
"return_type": "partial",
"customer": {
"id": 1,
"name": "Stored Customer"
},
"subtotal": {
"amount": "12.50",
"currency": "GHS",
"formatted": "GH\u20b512.50"
},
"tax_refund": {
"amount": "0.00",
"currency": "GHS",
"formatted": "GH\u20b50.00"
},
"total_refund": {
"amount": "12.50",
"currency": "GHS",
"formatted": "GH\u20b512.50"
},
"refunded": {
"amount": "0.00",
"currency": "GHS",
"formatted": "GH\u20b50.00"
},
"requires_approval": false,
"approved_at": null,
"completed_at": null,
"created_at": "2026-10-04T12: 00: 00+00: 00",
"reason_id": 1,
"reason_notes": null,
"rejection_reason": null,
"items": [
{
"id": 1,
"sale_item_id": 1,
"name": "Stored Product",
"sku": "STORED-SKU",
"warehouse_id": 1,
"quantity": "1.000",
"base_quantity": "1.000000",
"unit": {
"id": 1,
"name": "pc"
},
"condition": "resellable",
"restock": true,
"restocked": false,
"serial_number": null,
"refund": {
"amount": "12.50",
"currency": "GHS",
"formatted": "GH\u20b512.50"
},
"tax_refund": {
"amount": "0.00",
"currency": "GHS",
"formatted": "GH\u20b50.00"
}
}
],
"refund_plan": {
"requires_gateway": false,
"available": true,
"allocations": [
{
"payment_method_id": 1,
"name": "Cash",
"amount": {
"amount": "12.50",
"currency": "GHS",
"formatted": "GH\u20b512.50"
}
}
]
},
"refunds": [],
"allowed_actions": {
"approve": true,
"reject": true,
"cancel": true,
"process": true
}
}
}
}
{
"success": false,
"code": "validation_failed",
"message": "The given data was invalid.",
"errors": {
"store_id": [
"Select an accessible active store."
]
}
}
Requires a named staff-mobile bearer token (legacy seven-day or renewable 15-minute access), live permissions: web-orders.view. Renewable access is checked against its revocable device family on every request. Accept: application/json. Selected location and every order/sale line must be accessible; inaccessible records return 404. Cache-Control: private, no-store. Money and nonserialized quantities are decimal strings; serialized quantities are integers. Display dates in Africa/Accra.
| Parameter | In | Type | Description |
|---|---|---|---|
| store_id *required | query | integer | Selected accessible active store. |
| warehouse_id | query | integer | Optional selected accessible active warehouse. |
| queue | query | string | needs_action (default), in_progress, ready, completed, cancelled, awaiting_payment, all. |
| q | query | string | Search text, maximum 100 characters. Counts share search/location scope before queue selection. |
| page | query | integer | Page >= 1; fixed 25 rows. |
{
"store_id": 1,
"warehouse_id": 1,
"queue": "example",
"q": "example",
"page": 1
}
{
"success": true,
"data": {
"orders": [
{
"id": 1,
"number": "ORD-EXAMPLE-001",
"customer": {
"name": "Example Customer",
"has_whatsapp": true
},
"created_at": "2026-10-04T12: 00: 00+00: 00",
"status": {
"value": "new",
"label": "New \u00b7 confirm",
"tone": "amber"
},
"payment_status": {
"value": "unpaid",
"label": "Pay on pickup",
"tone": "gray"
},
"fulfillment_type": "pickup",
"total": {
"amount": "12.50",
"currency": "GHS",
"formatted": "GH\u20b512.50"
},
"items_preview": {
"name": "Example Product",
"image_url": "https://example.invalid/product.jpg",
"count": 1
},
"location": {
"store_name": "Example Store",
"warehouse_name": "Example Warehouse"
},
"waiting": {
"since": "2026-10-04T12: 00: 00+00: 00",
"label": "2 h",
"level": "late"
}
}
],
"queues": [
{
"key": "needs_action",
"label": "Needs action"
},
{
"key": "in_progress",
"label": "In progress"
},
{
"key": "ready",
"label": "Ready & out"
},
{
"key": "completed",
"label": "Completed"
},
{
"key": "cancelled",
"label": "Cancelled"
},
{
"key": "awaiting_payment",
"label": "Awaiting payment"
},
{
"key": "all",
"label": "All"
}
],
"counts": {
"needs_action": 1,
"in_progress": 0,
"ready": 0,
"completed": 0,
"cancelled": 0,
"awaiting_payment": 0,
"all": 1
},
"summary": {
"needs_action": [
{
"status": "new",
"label": "New \u00b7 confirm",
"count": 1
}
],
"unpaid": {
"count": 1,
"total": {
"amount": "12.50",
"currency": "GHS",
"formatted": "GH\u20b512.50"
}
}
}
},
"meta": {
"current_page": 1,
"last_page": 1,
"per_page": 25,
"total": 1
}
}
{
"success": false,
"code": "validation_failed",
"message": "The given data was invalid.",
"errors": {
"store_id": [
"Select an accessible active store."
]
}
}
Requires a named staff-mobile bearer token (legacy seven-day or renewable 15-minute access), live permissions: web-orders.view. Renewable access is checked against its revocable device family on every request. Accept: application/json. Selected location and every order/sale line must be accessible; inaccessible records return 404. Cache-Control: private, no-store. Money and nonserialized quantities are decimal strings; serialized quantities are integers. Display dates in Africa/Accra.
| Parameter | In | Type | Description |
|---|---|---|---|
| order *required | path | string | Path parameter: Order |
| store_id *required | query | integer | Selected accessible active store. |
| warehouse_id | query | integer | Optional selected accessible active warehouse. |
{
"store_id": 1,
"warehouse_id": 1
}
{
"success": true,
"data": {
"id": 1,
"number": "ORD-EXAMPLE-001",
"revision": "opaque-example-revision",
"customer": {
"name": "Example Customer",
"phone": null,
"email": "customer@example.invalid",
"notes": "",
"contact_links": {
"phone": null,
"whatsapp": null,
"email": "mailto:customer@example.invalid"
},
"addresses": [
{
"address_type": "shipping",
"full_name": "Example Customer",
"phone": "",
"email": "customer@example.invalid",
"line_1": "Example address",
"line_2": "",
"city": "Example City",
"state": "",
"postal_code": "",
"country": "GH"
}
]
},
"location": {
"store_id": 1,
"store_name": "Example Store",
"warehouse_id": 1,
"warehouse_name": "Example Warehouse"
},
"created_at": "2026-10-04T12: 00: 00+00: 00",
"status": {
"value": "new",
"label": "New \u00b7 confirm",
"tone": "amber"
},
"payment_status": {
"value": "unpaid",
"label": "Pay on pickup",
"tone": "gray"
},
"fulfillment_type": "pickup",
"waiting": {
"since": "2026-10-04T12: 00: 00+00: 00",
"label": "2 h",
"level": "late"
},
"progress": [
{
"label": "Placed",
"meta": "Oct 4, 12: 00 PM",
"state": "done",
"at": "2026-10-04T12: 00: 00+00: 00"
},
{
"label": "Confirm with customer",
"meta": "Next step",
"state": "current",
"at": null
},
{
"label": "Collected & paid",
"meta": "",
"state": "todo",
"at": null
}
],
"items": [
{
"id": 1,
"item_id": 1,
"name": "Example Product",
"sku": "EXAMPLE-001",
"image_url": null,
"quantity": "1",
"unit": "pc",
"is_serialized": true,
"available": true,
"warehouse_id": 1,
"unit_price": {
"amount": "12.50",
"currency": "GHS",
"formatted": "GH\u20b512.50"
},
"discount": {
"amount": "0.00",
"currency": "GHS",
"formatted": "GH\u20b50.00"
},
"tax": {
"amount": "0.00",
"currency": "GHS",
"formatted": "GH\u20b50.00"
},
"total": {
"amount": "12.50",
"currency": "GHS",
"formatted": "GH\u20b512.50"
}
}
],
"totals": {
"subtotal": {
"amount": "12.50",
"currency": "GHS",
"formatted": "GH\u20b512.50"
},
"discount": {
"amount": "0.00",
"currency": "GHS",
"formatted": "GH\u20b50.00"
},
"tax": {
"amount": "0.00",
"currency": "GHS",
"formatted": "GH\u20b50.00"
},
"delivery": {
"amount": "0.00",
"currency": "GHS",
"formatted": "GH\u20b50.00"
},
"total": {
"amount": "12.50",
"currency": "GHS",
"formatted": "GH\u20b512.50"
},
"delivery_fee_pending": false
},
"fulfillment": {
"status": "new",
"problem": null,
"courier_name": "",
"tracking_reference": "",
"tracking_url": null,
"scheduled_for": null,
"estimated_delivery_at": null,
"customer_message": "",
"notes": ""
},
"timeline": [
{
"id": "note-1",
"type": "note",
"title": "Staff note",
"message": "Example note",
"occurred_at": "2026-10-04T12: 00: 00+00: 00",
"author": "Example Staff"
}
],
"allowed_actions": {
"note": true,
"follow_up": true,
"follow_up_outcomes": [
{
"value": "contacted",
"label": "Confirmed with customer",
"hint": "Availability and arrangements are agreed. Record the payment on this order when the money arrives."
},
{
"value": "needs_customer_response",
"label": "Contacted, waiting on customer",
"hint": "You reached out and are waiting for a reply."
},
{
"value": "cancelled",
"label": "Cancel this order",
"hint": "The customer no longer wants it, or it cannot be supplied."
}
],
"settle": true,
"transition": null,
"receipt": false
},
"sale": null
}
}
{
"success": false,
"code": "validation_failed",
"message": "The given data was invalid.",
"errors": {
"store_id": [
"Select an accessible active store."
]
}
}
Requires a named staff-mobile bearer token (legacy seven-day or renewable 15-minute access), live permissions: web-orders.view, sales.create. Renewable access is checked against its revocable device family on every request. Accept: application/json. Selected location and every order/sale line must be accessible; inaccessible records return 404. Cache-Control: private, no-store. Money and nonserialized quantities are decimal strings; serialized quantities are integers. Display dates in Africa/Accra.
| Parameter | In | Type | Description |
|---|---|---|---|
| order *required | path | string | Path parameter: Order |
| store_id *required | body | integer | Selected accessible active store. |
| warehouse_id | body | integer | Optional selected accessible active warehouse. |
| delivery_fee | body | string | Decimal string, nonnegative, maximum 10 integer digits and 2 decimal digits; required if delivery fee is pending. |
| operation_key *required | body | string | UUID; identical completed operations resolve before revision checks. Retained 30 days; reconcile within 24 hours. |
| revision *required | body | string | Opaque revision from the current detail or quote; stale state returns 409. |
| payment_confirmed *required | body | boolean | Must be literal true; full payment already received. |
| payment_method_id *required | body | integer | Active store-compatible offline method; no credit. |
| payment_reference | body | string | Required for non-cash; maximum 255 characters. |
| amount_paid *required | body | string | Decimal string exactly equal to finalized total; no partial payments or gateway charges. |
| serials | body | object | Map of order line IDs to unique integer serial ID arrays; one serial per ordered unit. |
{
"store_id": 1,
"operation_key": "00000000-0000-4000-8000-000000000001",
"revision": "opaque-example-revision",
"payment_confirmed": true,
"payment_method_id": 1,
"amount_paid": "12.50",
"serials": {
"1": [
1
]
}
}
{
"success": true,
"data": {
"order": {
"id": 1,
"number": "ORD-EXAMPLE-001",
"revision": "opaque-example-revision",
"customer": {
"name": "Example Customer",
"phone": null,
"email": "customer@example.invalid",
"notes": "",
"contact_links": {
"phone": null,
"whatsapp": null,
"email": "mailto:customer@example.invalid"
},
"addresses": [
{
"address_type": "shipping",
"full_name": "Example Customer",
"phone": "",
"email": "customer@example.invalid",
"line_1": "Example address",
"line_2": "",
"city": "Example City",
"state": "",
"postal_code": "",
"country": "GH"
}
]
},
"location": {
"store_id": 1,
"store_name": "Example Store",
"warehouse_id": 1,
"warehouse_name": "Example Warehouse"
},
"created_at": "2026-10-04T12: 00: 00+00: 00",
"status": {
"value": "paid",
"label": "To prepare",
"tone": "amber"
},
"payment_status": {
"value": "paid",
"label": "Paid",
"tone": "green"
},
"fulfillment_type": "pickup",
"waiting": {
"since": "2026-10-04T12: 00: 00+00: 00",
"label": "2 h",
"level": "late"
},
"progress": [
{
"label": "Placed",
"meta": "Oct 4, 12: 00 PM",
"state": "done",
"at": "2026-10-04T12: 00: 00+00: 00"
},
{
"label": "Confirm with customer",
"meta": "Next step",
"state": "current",
"at": null
},
{
"label": "Collected & paid",
"meta": "",
"state": "todo",
"at": null
}
],
"items": [
{
"id": 1,
"item_id": 1,
"name": "Example Product",
"sku": "EXAMPLE-001",
"image_url": null,
"quantity": "1",
"unit": "pc",
"is_serialized": true,
"available": true,
"warehouse_id": 1,
"unit_price": {
"amount": "12.50",
"currency": "GHS",
"formatted": "GH\u20b512.50"
},
"discount": {
"amount": "0.00",
"currency": "GHS",
"formatted": "GH\u20b50.00"
},
"tax": {
"amount": "0.00",
"currency": "GHS",
"formatted": "GH\u20b50.00"
},
"total": {
"amount": "12.50",
"currency": "GHS",
"formatted": "GH\u20b512.50"
}
}
],
"totals": {
"subtotal": {
"amount": "12.50",
"currency": "GHS",
"formatted": "GH\u20b512.50"
},
"discount": {
"amount": "0.00",
"currency": "GHS",
"formatted": "GH\u20b50.00"
},
"tax": {
"amount": "0.00",
"currency": "GHS",
"formatted": "GH\u20b50.00"
},
"delivery": {
"amount": "0.00",
"currency": "GHS",
"formatted": "GH\u20b50.00"
},
"total": {
"amount": "12.50",
"currency": "GHS",
"formatted": "GH\u20b512.50"
},
"delivery_fee_pending": false
},
"fulfillment": {
"status": "paid",
"problem": null,
"courier_name": "",
"tracking_reference": "",
"tracking_url": null,
"scheduled_for": null,
"estimated_delivery_at": null,
"customer_message": "",
"notes": ""
},
"timeline": [
{
"id": "note-1",
"type": "note",
"title": "Staff note",
"message": "Example note",
"occurred_at": "2026-10-04T12: 00: 00+00: 00",
"author": "Example Staff"
}
],
"allowed_actions": {
"note": true,
"follow_up": false,
"follow_up_outcomes": [],
"settle": false,
"transition": {
"status": "preparing",
"label": "Start preparing"
},
"receipt": true
},
"sale": {
"id": 1,
"receipt_available": true
}
},
"operation": {
"key": "00000000-0000-4000-8000-000000000001",
"replayed": false,
"result_ids": {
"sale_id": 1,
"receipt_number": "RCP-EXAMPLE-001"
}
}
},
"message": "Payment recorded."
}
{
"success": false,
"code": "validation_failed",
"message": "The given data was invalid.",
"errors": {
"store_id": [
"Select an accessible active store."
]
}
}
Requires a named staff-mobile bearer token (legacy seven-day or renewable 15-minute access), live permissions: web-orders.view, fulfillment.manage. Renewable access is checked against its revocable device family on every request. Accept: application/json. Selected location and every order/sale line must be accessible; inaccessible records return 404. Cache-Control: private, no-store. Money and nonserialized quantities are decimal strings; serialized quantities are integers. Display dates in Africa/Accra.
| Parameter | In | Type | Description |
|---|---|---|---|
| order *required | path | string | Path parameter: Order |
| store_id *required | body | integer | Selected accessible active store. |
| warehouse_id | body | integer | Optional selected accessible active warehouse. |
| operation_key *required | body | string | UUID; identical completed operations resolve before revision checks. Retained 30 days; reconcile within 24 hours. |
| revision *required | body | string | Opaque revision from the current detail or quote; stale state returns 409. |
| status *required | body | string | Unpaid manual order: contacted, needs_customer_response, cancelled. |
| notes *required | body | string | Normalized nonblank plain text, maximum 2000 characters. |
{
"store_id": 1,
"operation_key": "00000000-0000-4000-8000-000000000001",
"revision": "opaque-example-revision",
"status": "contacted",
"notes": "Example customer follow-up"
}
{
"success": true,
"data": {
"order": {
"id": 1,
"number": "ORD-EXAMPLE-001",
"revision": "opaque-example-revision",
"customer": {
"name": "Example Customer",
"phone": null,
"email": "customer@example.invalid",
"notes": "",
"contact_links": {
"phone": null,
"whatsapp": null,
"email": "mailto:customer@example.invalid"
},
"addresses": [
{
"address_type": "shipping",
"full_name": "Example Customer",
"phone": "",
"email": "customer@example.invalid",
"line_1": "Example address",
"line_2": "",
"city": "Example City",
"state": "",
"postal_code": "",
"country": "GH"
}
]
},
"location": {
"store_id": 1,
"store_name": "Example Store",
"warehouse_id": 1,
"warehouse_name": "Example Warehouse"
},
"created_at": "2026-10-04T12: 00: 00+00: 00",
"status": {
"value": "contacted",
"label": "Contacted",
"tone": "indigo"
},
"payment_status": {
"value": "unpaid",
"label": "Pay on pickup",
"tone": "gray"
},
"fulfillment_type": "pickup",
"waiting": {
"since": "2026-10-04T12: 00: 00+00: 00",
"label": "2 h",
"level": "late"
},
"progress": [
{
"label": "Placed",
"meta": "Oct 4, 12: 00 PM",
"state": "done",
"at": "2026-10-04T12: 00: 00+00: 00"
},
{
"label": "Confirm with customer",
"meta": "Next step",
"state": "current",
"at": null
},
{
"label": "Collected & paid",
"meta": "",
"state": "todo",
"at": null
}
],
"items": [
{
"id": 1,
"item_id": 1,
"name": "Example Product",
"sku": "EXAMPLE-001",
"image_url": null,
"quantity": "1",
"unit": "pc",
"is_serialized": true,
"available": true,
"warehouse_id": 1,
"unit_price": {
"amount": "12.50",
"currency": "GHS",
"formatted": "GH\u20b512.50"
},
"discount": {
"amount": "0.00",
"currency": "GHS",
"formatted": "GH\u20b50.00"
},
"tax": {
"amount": "0.00",
"currency": "GHS",
"formatted": "GH\u20b50.00"
},
"total": {
"amount": "12.50",
"currency": "GHS",
"formatted": "GH\u20b512.50"
}
}
],
"totals": {
"subtotal": {
"amount": "12.50",
"currency": "GHS",
"formatted": "GH\u20b512.50"
},
"discount": {
"amount": "0.00",
"currency": "GHS",
"formatted": "GH\u20b50.00"
},
"tax": {
"amount": "0.00",
"currency": "GHS",
"formatted": "GH\u20b50.00"
},
"delivery": {
"amount": "0.00",
"currency": "GHS",
"formatted": "GH\u20b50.00"
},
"total": {
"amount": "12.50",
"currency": "GHS",
"formatted": "GH\u20b512.50"
},
"delivery_fee_pending": false
},
"fulfillment": {
"status": "contacted",
"problem": null,
"courier_name": "",
"tracking_reference": "",
"tracking_url": null,
"scheduled_for": null,
"estimated_delivery_at": null,
"customer_message": "",
"notes": ""
},
"timeline": [
{
"id": "note-1",
"type": "note",
"title": "Staff note",
"message": "Example note",
"occurred_at": "2026-10-04T12: 00: 00+00: 00",
"author": "Example Staff"
}
],
"allowed_actions": {
"note": true,
"follow_up": true,
"follow_up_outcomes": [
{
"value": "contacted",
"label": "Confirmed with customer",
"hint": "Availability and arrangements are agreed. Record the payment on this order when the money arrives."
},
{
"value": "needs_customer_response",
"label": "Contacted, waiting on customer",
"hint": "You reached out and are waiting for a reply."
},
{
"value": "cancelled",
"label": "Cancel this order",
"hint": "The customer no longer wants it, or it cannot be supplied."
}
],
"settle": true,
"transition": null,
"receipt": false
},
"sale": null
},
"operation": {
"key": "00000000-0000-4000-8000-000000000001",
"replayed": false,
"result_ids": {
"order_id": 1,
"note_id": 1,
"event_id": 1
}
}
},
"message": "Order updated."
}
{
"success": false,
"code": "validation_failed",
"message": "The given data was invalid.",
"errors": {
"store_id": [
"Select an accessible active store."
]
}
}
Requires a named staff-mobile bearer token (legacy seven-day or renewable 15-minute access), live permissions: web-orders.view, fulfillment.manage. Renewable access is checked against its revocable device family on every request. Accept: application/json. Selected location and every order/sale line must be accessible; inaccessible records return 404. Cache-Control: private, no-store. Money and nonserialized quantities are decimal strings; serialized quantities are integers. Display dates in Africa/Accra.
| Parameter | In | Type | Description |
|---|---|---|---|
| order *required | path | string | Path parameter: Order |
| store_id *required | body | integer | Selected accessible active store. |
| warehouse_id | body | integer | Optional selected accessible active warehouse. |
| operation_key *required | body | string | UUID; identical completed operations resolve before revision checks. Retained 30 days; reconcile within 24 hours. |
| revision *required | body | string | Opaque revision from the current detail or quote; stale state returns 409. |
| status *required | body | string | One permitted step. Pickup: paid -> preparing -> ready_for_pickup -> completed. Delivery: paid -> preparing -> out_for_delivery -> completed. |
| courier_name | body | string | Courier name, maximum 255 characters. |
| tracking_reference | body | string | Tracking reference, maximum 255 characters. |
| tracking_url | body | string | HTTPS tracking URL, maximum 2000 characters. |
| estimated_delivery_at | body | string | ISO date. |
| customer_message | body | string | Plain text customer message, maximum 2000 characters. |
| notes | body | string | Plain text, maximum 2000 characters. |
{
"store_id": 1,
"operation_key": "00000000-0000-4000-8000-000000000001",
"revision": "opaque-example-revision",
"status": "preparing"
}
{
"success": true,
"data": {
"order": {
"id": 1,
"number": "ORD-EXAMPLE-001",
"revision": "opaque-example-revision",
"customer": {
"name": "Example Customer",
"phone": null,
"email": "customer@example.invalid",
"notes": "",
"contact_links": {
"phone": null,
"whatsapp": null,
"email": "mailto:customer@example.invalid"
},
"addresses": [
{
"address_type": "shipping",
"full_name": "Example Customer",
"phone": "",
"email": "customer@example.invalid",
"line_1": "Example address",
"line_2": "",
"city": "Example City",
"state": "",
"postal_code": "",
"country": "GH"
}
]
},
"location": {
"store_id": 1,
"store_name": "Example Store",
"warehouse_id": 1,
"warehouse_name": "Example Warehouse"
},
"created_at": "2026-10-04T12: 00: 00+00: 00",
"status": {
"value": "preparing",
"label": "Preparing",
"tone": "indigo"
},
"payment_status": {
"value": "paid",
"label": "Paid",
"tone": "green"
},
"fulfillment_type": "pickup",
"waiting": {
"since": "2026-10-04T12: 00: 00+00: 00",
"label": "2 h",
"level": "late"
},
"progress": [
{
"label": "Placed",
"meta": "Oct 4, 12: 00 PM",
"state": "done",
"at": "2026-10-04T12: 00: 00+00: 00"
},
{
"label": "Confirm with customer",
"meta": "Next step",
"state": "current",
"at": null
},
{
"label": "Collected & paid",
"meta": "",
"state": "todo",
"at": null
}
],
"items": [
{
"id": 1,
"item_id": 1,
"name": "Example Product",
"sku": "EXAMPLE-001",
"image_url": null,
"quantity": "1",
"unit": "pc",
"is_serialized": true,
"available": true,
"warehouse_id": 1,
"unit_price": {
"amount": "12.50",
"currency": "GHS",
"formatted": "GH\u20b512.50"
},
"discount": {
"amount": "0.00",
"currency": "GHS",
"formatted": "GH\u20b50.00"
},
"tax": {
"amount": "0.00",
"currency": "GHS",
"formatted": "GH\u20b50.00"
},
"total": {
"amount": "12.50",
"currency": "GHS",
"formatted": "GH\u20b512.50"
}
}
],
"totals": {
"subtotal": {
"amount": "12.50",
"currency": "GHS",
"formatted": "GH\u20b512.50"
},
"discount": {
"amount": "0.00",
"currency": "GHS",
"formatted": "GH\u20b50.00"
},
"tax": {
"amount": "0.00",
"currency": "GHS",
"formatted": "GH\u20b50.00"
},
"delivery": {
"amount": "0.00",
"currency": "GHS",
"formatted": "GH\u20b50.00"
},
"total": {
"amount": "12.50",
"currency": "GHS",
"formatted": "GH\u20b512.50"
},
"delivery_fee_pending": false
},
"fulfillment": {
"status": "preparing",
"problem": null,
"courier_name": "",
"tracking_reference": "",
"tracking_url": null,
"scheduled_for": null,
"estimated_delivery_at": null,
"customer_message": "",
"notes": ""
},
"timeline": [
{
"id": "note-1",
"type": "note",
"title": "Staff note",
"message": "Example note",
"occurred_at": "2026-10-04T12: 00: 00+00: 00",
"author": "Example Staff"
}
],
"allowed_actions": {
"note": true,
"follow_up": false,
"follow_up_outcomes": [],
"settle": false,
"transition": {
"status": "ready_for_pickup",
"label": "Mark ready for pickup"
},
"receipt": true
},
"sale": {
"id": 1,
"receipt_available": true
}
},
"operation": {
"key": "00000000-0000-4000-8000-000000000001",
"replayed": false,
"result_ids": {
"fulfillment_id": 1,
"event_id": 1
}
}
},
"message": "Order updated."
}
{
"success": false,
"code": "validation_failed",
"message": "The given data was invalid.",
"errors": {
"store_id": [
"Select an accessible active store."
]
}
}
Requires a named staff-mobile bearer token (legacy seven-day or renewable 15-minute access), live permissions: web-orders.view, sales.create. Renewable access is checked against its revocable device family on every request. Accept: application/json. Selected location and every order/sale line must be accessible; inaccessible records return 404. Cache-Control: private, no-store. Money and nonserialized quantities are decimal strings; serialized quantities are integers. Display dates in Africa/Accra.
| Parameter | In | Type | Description |
|---|---|---|---|
| order *required | path | string | Path parameter: Order |
| orderItem *required | path | string | Path parameter: Order Item |
| store_id *required | query | integer | Selected accessible active store. |
| warehouse_id | query | integer | Optional selected accessible active warehouse. |
| q | query | string | Literal serial search, maximum 100 characters. |
| page | query | integer | Page >= 1; fixed maximum 50 serials. |
{
"store_id": 1,
"warehouse_id": 1,
"q": "example",
"page": 1
}
{
"success": true,
"data": {
"serials": [
{
"id": 1,
"serial_number": "EXAMPLE-SERIAL-001"
}
]
},
"meta": {
"current_page": 1,
"last_page": 1,
"per_page": 50,
"total": 1
}
}
{
"success": false,
"code": "validation_failed",
"message": "The given data was invalid.",
"errors": {
"store_id": [
"Select an accessible active store."
]
}
}
Requires a named staff-mobile bearer token (legacy seven-day or renewable 15-minute access), live permissions: web-orders.view, fulfillment.manage. Renewable access is checked against its revocable device family on every request. Accept: application/json. Selected location and every order/sale line must be accessible; inaccessible records return 404. Cache-Control: private, no-store. Money and nonserialized quantities are decimal strings; serialized quantities are integers. Display dates in Africa/Accra.
| Parameter | In | Type | Description |
|---|---|---|---|
| order *required | path | string | Path parameter: Order |
| store_id *required | body | integer | Selected accessible active store. |
| warehouse_id | body | integer | Optional selected accessible active warehouse. |
| operation_key *required | body | string | UUID; identical completed operations resolve before revision checks. Retained 30 days; reconcile within 24 hours. |
| revision *required | body | string | Opaque revision from the current detail or quote; stale state returns 409. |
| body *required | body | string | Normalized nonblank plain text, maximum 2000 characters. |
{
"store_id": 1,
"operation_key": "00000000-0000-4000-8000-000000000001",
"revision": "opaque-example-revision",
"body": "Example internal note"
}
{
"success": true,
"data": {
"order": {
"id": 1,
"number": "ORD-EXAMPLE-001",
"revision": "opaque-example-revision",
"customer": {
"name": "Example Customer",
"phone": null,
"email": "customer@example.invalid",
"notes": "",
"contact_links": {
"phone": null,
"whatsapp": null,
"email": "mailto:customer@example.invalid"
},
"addresses": [
{
"address_type": "shipping",
"full_name": "Example Customer",
"phone": "",
"email": "customer@example.invalid",
"line_1": "Example address",
"line_2": "",
"city": "Example City",
"state": "",
"postal_code": "",
"country": "GH"
}
]
},
"location": {
"store_id": 1,
"store_name": "Example Store",
"warehouse_id": 1,
"warehouse_name": "Example Warehouse"
},
"created_at": "2026-10-04T12: 00: 00+00: 00",
"status": {
"value": "new",
"label": "New \u00b7 confirm",
"tone": "amber"
},
"payment_status": {
"value": "unpaid",
"label": "Pay on pickup",
"tone": "gray"
},
"fulfillment_type": "pickup",
"waiting": {
"since": "2026-10-04T12: 00: 00+00: 00",
"label": "2 h",
"level": "late"
},
"progress": [
{
"label": "Placed",
"meta": "Oct 4, 12: 00 PM",
"state": "done",
"at": "2026-10-04T12: 00: 00+00: 00"
},
{
"label": "Confirm with customer",
"meta": "Next step",
"state": "current",
"at": null
},
{
"label": "Collected & paid",
"meta": "",
"state": "todo",
"at": null
}
],
"items": [
{
"id": 1,
"item_id": 1,
"name": "Example Product",
"sku": "EXAMPLE-001",
"image_url": null,
"quantity": "1",
"unit": "pc",
"is_serialized": true,
"available": true,
"warehouse_id": 1,
"unit_price": {
"amount": "12.50",
"currency": "GHS",
"formatted": "GH\u20b512.50"
},
"discount": {
"amount": "0.00",
"currency": "GHS",
"formatted": "GH\u20b50.00"
},
"tax": {
"amount": "0.00",
"currency": "GHS",
"formatted": "GH\u20b50.00"
},
"total": {
"amount": "12.50",
"currency": "GHS",
"formatted": "GH\u20b512.50"
}
}
],
"totals": {
"subtotal": {
"amount": "12.50",
"currency": "GHS",
"formatted": "GH\u20b512.50"
},
"discount": {
"amount": "0.00",
"currency": "GHS",
"formatted": "GH\u20b50.00"
},
"tax": {
"amount": "0.00",
"currency": "GHS",
"formatted": "GH\u20b50.00"
},
"delivery": {
"amount": "0.00",
"currency": "GHS",
"formatted": "GH\u20b50.00"
},
"total": {
"amount": "12.50",
"currency": "GHS",
"formatted": "GH\u20b512.50"
},
"delivery_fee_pending": false
},
"fulfillment": {
"status": "new",
"problem": null,
"courier_name": "",
"tracking_reference": "",
"tracking_url": null,
"scheduled_for": null,
"estimated_delivery_at": null,
"customer_message": "",
"notes": ""
},
"timeline": [
{
"id": "note-1",
"type": "note",
"title": "Staff note",
"message": "Example note",
"occurred_at": "2026-10-04T12: 00: 00+00: 00",
"author": "Example Staff"
}
],
"allowed_actions": {
"note": true,
"follow_up": true,
"follow_up_outcomes": [
{
"value": "contacted",
"label": "Confirmed with customer",
"hint": "Availability and arrangements are agreed. Record the payment on this order when the money arrives."
},
{
"value": "needs_customer_response",
"label": "Contacted, waiting on customer",
"hint": "You reached out and are waiting for a reply."
},
{
"value": "cancelled",
"label": "Cancel this order",
"hint": "The customer no longer wants it, or it cannot be supplied."
}
],
"settle": true,
"transition": null,
"receipt": false
},
"sale": null
},
"operation": {
"key": "00000000-0000-4000-8000-000000000001",
"replayed": false,
"result_ids": {
"note_id": 1
}
}
},
"message": "Order updated."
}
{
"success": false,
"code": "validation_failed",
"message": "The given data was invalid.",
"errors": {
"store_id": [
"Select an accessible active store."
]
}
}
Requires a named staff-mobile bearer token (legacy seven-day or renewable 15-minute access), live permissions: web-orders.view, sales.create. Renewable access is checked against its revocable device family on every request. Accept: application/json. Selected location and every order/sale line must be accessible; inaccessible records return 404. Cache-Control: private, no-store. Money and nonserialized quantities are decimal strings; serialized quantities are integers. Display dates in Africa/Accra.
| Parameter | In | Type | Description |
|---|---|---|---|
| order *required | path | string | Path parameter: Order |
| store_id *required | query | integer | Selected accessible active store. |
| warehouse_id | query | integer | Optional selected accessible active warehouse. |
{
"store_id": 1,
"warehouse_id": 1
}
{
"success": true,
"data": {
"order_id": 1,
"revision": "opaque-example-revision",
"totals": {
"subtotal": {
"amount": "12.50",
"currency": "GHS",
"formatted": "GH\u20b512.50"
},
"discount": {
"amount": "0.00",
"currency": "GHS",
"formatted": "GH\u20b50.00"
},
"tax": {
"amount": "0.00",
"currency": "GHS",
"formatted": "GH\u20b50.00"
},
"delivery": {
"amount": "0.00",
"currency": "GHS",
"formatted": "GH\u20b50.00"
},
"total": {
"amount": "12.50",
"currency": "GHS",
"formatted": "GH\u20b512.50"
},
"delivery_fee_pending": false
},
"payment_methods": [
{
"id": 1,
"name": "Cash",
"code": "cash",
"requires_reference": false
}
],
"serialized_lines": [
{
"id": 1,
"item_id": 1,
"store_id": 1,
"warehouse_id": 1,
"title": "Example Product",
"quantity": 1
}
]
}
}
{
"success": false,
"code": "validation_failed",
"message": "The given data was invalid.",
"errors": {
"store_id": [
"Select an accessible active store."
]
}
}
Requires a named staff-mobile bearer token (legacy seven-day or renewable 15-minute access), live permissions: web-orders.view, sales.create. Renewable access is checked against its revocable device family on every request. Accept: application/json. Selected location and every order/sale line must be accessible; inaccessible records return 404. Cache-Control: private, no-store. Money and nonserialized quantities are decimal strings; serialized quantities are integers. Display dates in Africa/Accra.
| Parameter | In | Type | Description |
|---|---|---|---|
| order *required | path | string | Path parameter: Order |
| store_id *required | body | integer | Selected accessible active store. |
| warehouse_id | body | integer | Optional selected accessible active warehouse. |
| delivery_fee | body | string | Decimal string, nonnegative, maximum 10 integer digits and 2 decimal digits; required if delivery fee is pending. |
{
"store_id": 1,
"delivery_fee": "0.00"
}
{
"success": true,
"data": {
"totals": {
"subtotal": {
"amount": "12.50",
"currency": "GHS",
"formatted": "GH\u20b512.50"
},
"discount": {
"amount": "0.00",
"currency": "GHS",
"formatted": "GH\u20b50.00"
},
"tax": {
"amount": "0.00",
"currency": "GHS",
"formatted": "GH\u20b50.00"
},
"delivery": {
"amount": "0.00",
"currency": "GHS",
"formatted": "GH\u20b50.00"
},
"total": {
"amount": "12.50",
"currency": "GHS",
"formatted": "GH\u20b512.50"
},
"delivery_fee_pending": false
},
"revision": "opaque-example-revision"
}
}
{
"success": false,
"code": "validation_failed",
"message": "The given data was invalid.",
"errors": {
"store_id": [
"Select an accessible active store."
]
}
}
Authentication
User authentication and session management.
Authenticate a user and issue a Sanctum token for API requests.
| Parameter | In | Type | Description |
|---|---|---|---|
| email *required | body | string | User email address |
| password *required | body | string | User password |
| device_name | body | string | Optional device label |
| store_id | body | integer | Preferred store context for login |
| two_factor_code | body | string | Two-factor code when required |
{
"email": "cashier@example.com",
"password": "secret123",
"device_name": "iPhone 15 Pro",
"store_id": 1
}
{
"success": true,
"data": {
"token": "1|abc123xyz...",
"user": {
"id": 1,
"name": "John Doe",
"email": "cashier@example.com",
"role": {
"id": 2,
"name": "Cashier",
"slug": "cashier"
},
"store_id": 1,
"store_name": "Main Store",
"is_super_admin": false,
"has_two_factor": false
},
"permissions": [
"pos.access",
"sales.create",
"customers.view"
],
"store_id": 1
}
}
{
"success": false,
"message": "The given data was invalid.",
"errors": {
"email": [
"The provided credentials are incorrect."
]
}
}
Revoke the current token and end the current API session.
[]
{
"success": true,
"data": {
"message": "Successfully logged out."
}
}
{
"success": false,
"message": "Unauthorized."
}
Return the authenticated user profile, permissions, and discount limits.
[]
{
"success": true,
"data": {
"id": 1,
"name": "Example"
}
}
{
"success": false,
"message": "Unauthorized."
}
Products & Catalog
Items, categories, stores, warehouses, and reference data.
Get paginated categories with optional hierarchy and count metadata.
[]
{
"success": true,
"data": [
{
"id": 1,
"name": "Example"
}
]
}
{
"success": false,
"message": "Unauthorized."
}
Submit a Categories request to /categories.
[]
{
"success": true,
"data": [
{
"id": 1,
"name": "Example"
}
]
}
{
"success": false,
"message": "Unauthorized."
}
Delete Categories data through /categories/{category}.
| Parameter | In | Type | Description |
|---|---|---|---|
| category *required | path | string | Path parameter: Category |
[]
{
"success": true,
"message": "Operation completed successfully."
}
{
"success": false,
"message": "Unauthorized."
}
Retrieve Categories data from /categories/{category}.
| Parameter | In | Type | Description |
|---|---|---|---|
| category *required | path | string | Path parameter: Category |
[]
{
"success": true,
"data": {
"id": 1,
"name": "Example"
}
}
{
"success": false,
"message": "Unauthorized."
}
Update Categories data through /categories/{category}.
| Parameter | In | Type | Description |
|---|---|---|---|
| category *required | path | string | Path parameter: Category |
[]
{
"success": true,
"data": {
"id": 1,
"name": "Example"
}
}
{
"success": false,
"message": "Unauthorized."
}
Get paginated items with optional search by query, SKU, barcode, or related catalog metadata.
| Parameter | In | Type | Description |
|---|---|---|---|
| q | query | string | Search query |
| search | query | string | Legacy search parameter alias for q |
| per_page | query | integer | Results per page (default: 50, max: 100) |
{
"q": "example",
"search": "example",
"per_page": 1
}
{
"success": true,
"data": [
{
"id": 1,
"name": "Example"
}
],
"meta": {
"current_page": 1,
"last_page": 1,
"per_page": 50,
"total": 1
}
}
{
"success": false,
"message": "Unauthorized."
}
Retrieve Items data from /items/check-name.
[]
{
"success": true,
"data": {
"id": 1,
"name": "Example"
}
}
{
"success": false,
"message": "Unauthorized."
}
Retrieve Items data from /items/{item}/applicable-price-books.
| Parameter | In | Type | Description |
|---|---|---|---|
| item *required | path | string | Path parameter: Item |
[]
{
"success": true,
"data": {
"id": 1,
"name": "Example"
}
}
{
"success": false,
"message": "Unauthorized."
}
Retrieve Items data from /items/{item}/price.
| Parameter | In | Type | Description |
|---|---|---|---|
| item *required | path | string | Path parameter: Item |
[]
{
"success": true,
"data": {
"id": 1,
"name": "Example"
}
}
{
"success": false,
"message": "Unauthorized."
}
Retrieve Items data from /items/{item}/price-breakdown.
| Parameter | In | Type | Description |
|---|---|---|---|
| item *required | path | string | Path parameter: Item |
[]
{
"success": true,
"data": {
"id": 1,
"name": "Example"
}
}
{
"success": false,
"message": "Unauthorized."
}
Retrieve Payment Methods data from /payment-methods.
[]
{
"success": true,
"data": [
{
"id": 1,
"name": "Example"
}
]
}
{
"success": false,
"message": "Unauthorized."
}
Retrieve Payment Methods data from /payment-methods/{paymentMethod}.
| Parameter | In | Type | Description |
|---|---|---|---|
| paymentMethod *required | path | string | Path parameter: Payment Method |
[]
{
"success": true,
"data": {
"id": 1,
"name": "Example"
}
}
{
"success": false,
"message": "Unauthorized."
}
Retrieve Price Books data from /price-books.
[]
{
"success": true,
"data": [
{
"id": 1,
"name": "Example"
}
]
}
{
"success": false,
"message": "Unauthorized."
}
Retrieve Price Books data from /price-books/{priceBook}.
| Parameter | In | Type | Description |
|---|---|---|---|
| priceBook *required | path | string | Path parameter: Price Book |
[]
{
"success": true,
"data": {
"id": 1,
"name": "Example"
}
}
{
"success": false,
"message": "Unauthorized."
}
Return stores the authenticated user can access, with optional warehouses and payment methods.
[]
{
"success": true,
"data": [
{
"id": 1,
"name": "Example"
}
]
}
{
"success": false,
"message": "Unauthorized."
}
Retrieve Stores data from /stores/{store}.
| Parameter | In | Type | Description |
|---|---|---|---|
| store *required | path | string | Path parameter: Store |
[]
{
"success": true,
"data": {
"id": 1,
"name": "Example"
}
}
{
"success": false,
"message": "Unauthorized."
}
Return active warehouses for a specific store, optionally including standalone warehouses that serve it.
| Parameter | In | Type | Description |
|---|---|---|---|
| store *required | path | string | Path parameter: Store |
[]
{
"success": true,
"data": {
"id": 1,
"name": "Example"
}
}
{
"success": false,
"message": "Unauthorized."
}
Return active warehouses with store and standalone filtering options.
[]
{
"success": true,
"data": [
{
"id": 1,
"name": "Example"
}
]
}
{
"success": false,
"message": "Unauthorized."
}
Retrieve Warehouses data from /warehouses/standalone.
[]
{
"success": true,
"data": {
"id": 1,
"name": "Example"
}
}
{
"success": false,
"message": "Unauthorized."
}
Retrieve Warehouses data from /warehouses/{warehouse}.
| Parameter | In | Type | Description |
|---|---|---|---|
| warehouse *required | path | string | Path parameter: Warehouse |
[]
{
"success": true,
"data": {
"id": 1,
"name": "Example"
}
}
{
"success": false,
"message": "Unauthorized."
}
POS Operations
POS catalog, checkout, tax, discounts, and session status.
Validate, price, tax, and complete a POS sale.
| Parameter | In | Type | Description |
|---|---|---|---|
| items *required | body | array | Field: Items |
| items.*.id *required | body | string | Field: Items * Id |
| items.*.qty *required | body | number | Field: Items * Qty |
| items.*.serial_number | body | string | Field: Items * Serial Number |
| items.*.price | body | number | Field: Items * Price |
| items.*.unit_id | body | string | Field: Items * Unit Id |
| items.*.base_quantity | body | number | Field: Items * Base Quantity |
| items.*.warehouse_id | body | integer | Field: Items * Warehouse Id |
| items.*.discount_type | body | string | Field: Items * Discount Type |
| items.*.discount_value | body | number | Field: Items * Discount Value |
| items.*.discount_amount | body | number | Field: Items * Discount Amount |
| items.*.discount_reason | body | string | Field: Items * Discount Reason |
| items.*.discount_approved_by | body | string | Field: Items * Discount Approved By |
| items.*.discount_approval_token | body | string | Field: Items * Discount Approval Token |
| payments *required | body | array | Field: Payments |
| payments.*.payment_method_code *required | body | string | Field: Payments * Payment Method Code |
| payments.*.payment_method_id | body | string | Field: Payments * Payment Method Id |
| payments.*.amount *required | body | number | Field: Payments * Amount |
| payments.*.tendered | body | number | Field: Payments * Tendered |
| payments.*.change | body | number | Field: Payments * Change |
| payments.*.reference | body | string | Field: Payments * Reference |
| payment_method | body | string | Field: Payment Method |
| payment_method_id | body | string | Field: Payment Method Id |
| total_amount *required | body | number | Field: Total Amount |
| subtotal | body | number | Field: Subtotal |
| customer_id | body | string | Field: Customer Id |
| reference | body | string | Field: Reference |
| cart_discount_type | body | string | Field: Cart Discount Type |
| cart_discount_value | body | number | Field: Cart Discount Value |
| cart_discount_amount | body | number | Field: Cart Discount Amount |
| cart_discount_reason | body | string | Field: Cart Discount Reason |
| cart_discount_approved_by | body | string | Field: Cart Discount Approved By |
| cart_discount_approval_token | body | string | Field: Cart Discount Approval Token |
| total_discount | body | number | Field: Total Discount |
| tax_exempt | body | boolean | Field: Tax Exempt |
| idempotency_key *required | body | string | Field: Idempotency Key |
| store_id *required | body | integer | Field: Store Id |
| offline_created_at | body | date | Field: Offline Created At |
{
"items": [],
"items.*.id": "example",
"items.*.qty": 9.99,
"items.*.serial_number": "example",
"items.*.price": 9.99,
"items.*.unit_id": "example",
"items.*.base_quantity": 9.99,
"items.*.warehouse_id": 1,
"items.*.discount_type": "example",
"items.*.discount_value": 9.99,
"items.*.discount_amount": 9.99,
"items.*.discount_reason": "example",
"items.*.discount_approved_by": "example",
"items.*.discount_approval_token": "example",
"payments": [],
"payments.*.payment_method_code": "example",
"payments.*.payment_method_id": "example",
"payments.*.amount": 9.99,
"payments.*.tendered": 9.99,
"payments.*.change": 9.99,
"payments.*.reference": "example",
"payment_method": "example",
"payment_method_id": "example",
"total_amount": 9.99,
"subtotal": 9.99,
"customer_id": "example",
"reference": "example",
"cart_discount_type": "example",
"cart_discount_value": 9.99,
"cart_discount_amount": 9.99,
"cart_discount_reason": "example",
"cart_discount_approved_by": "example",
"cart_discount_approval_token": "example",
"total_discount": 9.99,
"tax_exempt": true,
"idempotency_key": "example",
"store_id": 1,
"offline_created_at": "2026-03-16"
}
{
"success": true,
"data": {
"id": 1,
"name": "Example"
}
}
{
"success": false,
"message": "Unauthorized."
}
Submit a Pos Checkout request to /pos/checkout/validate.
[]
{
"success": true,
"data": {
"id": 1,
"name": "Example"
}
}
{
"success": false,
"message": "Unauthorized."
}
Retrieve Pos data from /pos/discount-limits.
[]
{
"success": true,
"data": [
{
"id": 1,
"name": "Example"
}
]
}
{
"success": false,
"message": "Unauthorized."
}
Submit a Pos Discount request to /pos/discount/approve.
[]
{
"success": true,
"data": {
"id": 1,
"name": "Example"
}
}
{
"success": false,
"message": "Unauthorized."
}
Get paginated POS-ready products with live price and stock data.
[]
{
"success": true,
"data": [
{
"id": 1,
"name": "Example"
}
]
}
{
"success": false,
"message": "Unauthorized."
}
Search POS products by name, SKU, or barcode.
| Parameter | In | Type | Description |
|---|---|---|---|
| q *required | query | string | Field: Q |
| store_id *required | query | integer | Field: Store Id |
| warehouse_id | query | integer | Field: Warehouse Id |
| category_id | query | integer | Field: Category Id |
| limit | query | integer | Field: Limit |
{
"q": "example",
"store_id": 1,
"warehouse_id": 1,
"category_id": 1,
"limit": 1
}
{
"success": true,
"data": [
{
"id": 1,
"name": "Example"
}
]
}
{
"success": false,
"message": "Unauthorized."
}
Retrieve Pos Products data from /pos/products/{item}.
| Parameter | In | Type | Description |
|---|---|---|---|
| item *required | path | string | Path parameter: Item |
[]
{
"success": true,
"data": {
"id": 1,
"name": "Example"
}
}
{
"success": false,
"message": "Unauthorized."
}
Return POS readiness, including store access and active cashier session status.
[]
{
"success": true,
"data": [
{
"id": 1,
"name": "Example"
}
]
}
{
"success": false,
"message": "Unauthorized."
}
Return tax details for a POS cart before checkout.
[]
{
"success": true,
"data": {
"id": 1,
"name": "Example"
}
}
{
"success": false,
"message": "Unauthorized."
}
Retrieve Pos Tax data from /pos/tax/info.
[]
{
"success": true,
"data": {
"id": 1,
"name": "Example"
}
}
{
"success": false,
"message": "Unauthorized."
}
Cashier Sessions
Cashier shift open, close, suspend, and cash movement APIs.
Return the open cashier session, if any, for the current user and store.
[]
{
"success": true,
"data": {
"id": 1,
"name": "Example"
}
}
{
"success": false,
"message": "Unauthorized."
}
Open a new cashier session for the authenticated user.
| Parameter | In | Type | Description |
|---|---|---|---|
| store_id *required | body | integer | Field: Store Id |
| opening_balance *required | body | number | Field: Opening Balance |
| cash_register_id | body | integer | Field: Cash Register Id |
| notes | body | string | Field: Notes |
{
"store_id": 1,
"opening_balance": 9.99,
"cash_register_id": 1,
"notes": "example"
}
{
"success": true,
"data": {
"id": 1,
"name": "Example"
}
}
{
"success": false,
"message": "Unauthorized."
}
Retrieve Sessions data from /sessions/{session}.
| Parameter | In | Type | Description |
|---|---|---|---|
| session *required | path | string | Path parameter: Session |
[]
{
"success": true,
"data": {
"id": 1,
"name": "Example"
}
}
{
"success": false,
"message": "Unauthorized."
}
Submit a Sessions request to /sessions/{session}/cash-movement.
| Parameter | In | Type | Description |
|---|---|---|---|
| session *required | path | string | Path parameter: Session |
| type *required | body | string | Field: Type |
| amount *required | body | number | Field: Amount |
| reason *required | body | string | Field: Reason |
| notes | body | string | Field: Notes |
| reference | body | string | Field: Reference |
{
"type": "example",
"amount": 9.99,
"reason": "example",
"notes": "example",
"reference": "example"
}
{
"success": true,
"data": {
"id": 1,
"name": "Example"
}
}
{
"success": false,
"message": "Unauthorized."
}
Submit a Sessions request to /sessions/{session}/close.
| Parameter | In | Type | Description |
|---|---|---|---|
| session *required | path | string | Path parameter: Session |
| closing_balance *required | body | number | Field: Closing Balance |
| notes | body | string | Field: Notes |
| discrepancy_notes | body | string | Field: Discrepancy Notes |
{
"closing_balance": 9.99,
"notes": "example",
"discrepancy_notes": "example"
}
{
"success": true,
"data": {
"id": 1,
"name": "Example"
}
}
{
"success": false,
"message": "Unauthorized."
}
Submit a Sessions request to /sessions/{session}/resume.
| Parameter | In | Type | Description |
|---|---|---|---|
| session *required | path | string | Path parameter: Session |
[]
{
"success": true,
"data": {
"id": 1,
"name": "Example"
}
}
{
"success": false,
"message": "Unauthorized."
}
Return the summary rollup for a cashier session.
| Parameter | In | Type | Description |
|---|---|---|---|
| session *required | path | string | Path parameter: Session |
[]
{
"success": true,
"data": {
"id": 1,
"name": "Example"
}
}
{
"success": false,
"message": "Unauthorized."
}
Submit a Sessions request to /sessions/{session}/suspend.
| Parameter | In | Type | Description |
|---|---|---|---|
| session *required | path | string | Path parameter: Session |
[]
{
"success": true,
"data": {
"id": 1,
"name": "Example"
}
}
{
"success": false,
"message": "Unauthorized."
}
Customers
Customer search, listing, and CRUD operations.
Get paginated customers with status and search filters.
[]
{
"success": true,
"data": [
{
"id": 1,
"name": "Example"
}
]
}
{
"success": false,
"message": "Unauthorized."
}
Submit a Customers request to /customers.
| Parameter | In | Type | Description |
|---|---|---|---|
| name *required | body | string | Field: Name |
| body | string | Field: Email | |
| phone | body | string | Field: Phone |
| address | body | string | Field: Address |
| customer_group_id | body | integer | Field: Customer Group Id |
{
"name": "example",
"email": "user@example.com",
"phone": "example",
"address": "example",
"customer_group_id": 1
}
{
"success": true,
"data": [
{
"id": 1,
"name": "Example"
}
]
}
{
"success": false,
"message": "Unauthorized."
}
Search customers by name, phone, email, or address for quick POS selection.
[]
{
"success": true,
"data": [
{
"id": 1,
"name": "Example"
}
]
}
{
"success": false,
"message": "Unauthorized."
}
Delete Customers data through /customers/{customer}.
| Parameter | In | Type | Description |
|---|---|---|---|
| customer *required | path | string | Path parameter: Customer |
[]
{
"success": true,
"message": "Operation completed successfully."
}
{
"success": false,
"message": "Unauthorized."
}
Retrieve Customers data from /customers/{customer}.
| Parameter | In | Type | Description |
|---|---|---|---|
| customer *required | path | string | Path parameter: Customer |
[]
{
"success": true,
"data": {
"id": 1,
"name": "Example"
}
}
{
"success": false,
"message": "Unauthorized."
}
Update Customers data through /customers/{customer}.
| Parameter | In | Type | Description |
|---|---|---|---|
| customer *required | path | string | Path parameter: Customer |
[]
{
"success": true,
"data": {
"id": 1,
"name": "Example"
}
}
{
"success": false,
"message": "Unauthorized."
}
Sales
Sales history, receipt, and search endpoints.
Retrieve Sales data from /sales.
[]
{
"success": true,
"data": [
{
"id": 1,
"name": "Example"
}
]
}
{
"success": false,
"message": "Unauthorized."
}
Retrieve Sales data from /sales/search.
[]
{
"success": true,
"data": [
{
"id": 1,
"name": "Example"
}
]
}
{
"success": false,
"message": "Unauthorized."
}
Retrieve Sales data from /sales/{sale}.
| Parameter | In | Type | Description |
|---|---|---|---|
| sale *required | path | string | Path parameter: Sale |
[]
{
"success": true,
"data": {
"id": 1,
"name": "Example"
}
}
{
"success": false,
"message": "Unauthorized."
}
Retrieve Sales data from /sales/{sale}/receipt.
| Parameter | In | Type | Description |
|---|---|---|---|
| sale *required | path | string | Path parameter: Sale |
[]
{
"success": true,
"data": {
"id": 1,
"name": "Example"
}
}
{
"success": false,
"message": "Unauthorized."
}
Returns
Return validation, creation, approval, and lookup endpoints.
Get paginated returns scoped by store, warehouse, status, date, and search filters.
[]
{
"success": true,
"data": [
{
"id": 1,
"name": "Example"
}
]
}
{
"success": false,
"message": "Unauthorized."
}
Submit a Returns request to /returns.
| Parameter | In | Type | Description |
|---|---|---|---|
| sale_id *required | body | string | Field: Sale Id |
| items *required | body | array | Field: Items |
| items.*.sale_item_id *required | body | string | Field: Items * Sale Item Id |
| items.*.quantity *required | body | integer | Field: Items * Quantity |
| items.*.condition | body | string | Field: Items * Condition |
| items.*.restock | body | boolean | Field: Items * Restock |
| items.*.notes | body | string | Field: Items * Notes |
| items.*.warehouse_id | body | string | Field: Items * Warehouse Id |
| reason_id | body | string | Field: Reason Id |
| reason_notes | body | string | Field: Reason Notes |
| internal_notes | body | string | Field: Internal Notes |
| process_immediately | body | boolean | Field: Process Immediately |
| idempotency_key | body | string | Field: Idempotency Key |
{
"sale_id": "example",
"items": [],
"items.*.sale_item_id": "example",
"items.*.quantity": 1,
"items.*.condition": "example",
"items.*.restock": true,
"items.*.notes": "example",
"items.*.warehouse_id": "example",
"reason_id": "example",
"reason_notes": "example",
"internal_notes": "example",
"process_immediately": true,
"idempotency_key": "example"
}
{
"success": true,
"data": [
{
"id": 1,
"name": "Example"
}
]
}
{
"success": false,
"message": "Unauthorized."
}
Validate whether a sale can be returned and return returnable items when eligible.
[]
{
"success": true,
"data": {
"id": 1,
"name": "Example"
}
}
{
"success": false,
"message": "Unauthorized."
}
Return active return reasons for return creation flows.
[]
{
"success": true,
"data": [
{
"id": 1,
"name": "Example"
}
]
}
{
"success": false,
"message": "Unauthorized."
}
Retrieve Returns data from /returns/{saleReturn}.
| Parameter | In | Type | Description |
|---|---|---|---|
| saleReturn *required | path | string | Path parameter: Sale Return |
[]
{
"success": true,
"data": {
"id": 1,
"name": "Example"
}
}
{
"success": false,
"message": "Unauthorized."
}
Submit a Returns request to /returns/{saleReturn}/approve.
| Parameter | In | Type | Description |
|---|---|---|---|
| saleReturn *required | path | string | Path parameter: Sale Return |
[]
{
"success": true,
"data": {
"id": 1,
"name": "Example"
}
}
{
"success": false,
"message": "Unauthorized."
}
Submit a Returns request to /returns/{saleReturn}/cancel.
| Parameter | In | Type | Description |
|---|---|---|---|
| saleReturn *required | path | string | Path parameter: Sale Return |
[]
{
"success": true,
"data": {
"id": 1,
"name": "Example"
}
}
{
"success": false,
"message": "Unauthorized."
}
Submit a Returns request to /returns/{saleReturn}/process.
| Parameter | In | Type | Description |
|---|---|---|---|
| saleReturn *required | path | string | Path parameter: Sale Return |
[]
{
"success": true,
"data": {
"id": 1,
"name": "Example"
}
}
{
"success": false,
"message": "Unauthorized."
}
Submit a Returns request to /returns/{saleReturn}/reject.
| Parameter | In | Type | Description |
|---|---|---|---|
| saleReturn *required | path | string | Path parameter: Sale Return |
[]
{
"success": true,
"data": {
"id": 1,
"name": "Example"
}
}
{
"success": false,
"message": "Unauthorized."
}
Offline Sync
Initial sync, delta sync, upload, and sync status APIs.
Return only records changed since the provided timestamp.
[]
{
"success": true,
"data": {
"id": 1,
"name": "Example"
}
}
{
"success": false,
"message": "Unauthorized."
}
Return the full offline payload needed to bootstrap a device.
[]
{
"success": true,
"data": {
"id": 1,
"name": "Example"
}
}
{
"success": false,
"message": "Unauthorized."
}
Return sync health, pending uploads, and related status metadata.
[]
{
"success": true,
"data": {
"id": 1,
"name": "Example"
}
}
{
"success": false,
"message": "Unauthorized."
}
Upload offline-created transactions in bulk for server reconciliation.
| Parameter | In | Type | Description |
|---|---|---|---|
| store_id *required | body | integer | Field: Store Id |
| transactions *required | body | array | Field: Transactions |
| transactions.*.idempotency_key *required | body | string | Field: Transactions * Idempotency Key |
| transactions.*.type *required | body | string | Field: Transactions * Type |
| transactions.*.offline_created_at | body | date | Field: Transactions * Offline Created At |
| transactions.*.data *required | body | array | Field: Transactions * Data |
{
"store_id": 1,
"transactions": [],
"transactions.*.idempotency_key": "example",
"transactions.*.type": "example",
"transactions.*.offline_created_at": "2026-03-16",
"transactions.*.data": []
}
{
"success": true,
"data": {
"id": 1,
"name": "Example"
}
}
{
"success": false,
"message": "Unauthorized."
}
Pricing & Bundles
Price books, pricing rules, pricing calculators, bundles, and currencies.
Get paginated bundles with optional store and validity filters.
[]
{
"success": true,
"data": [
{
"id": 1,
"name": "Example"
}
]
}
{
"success": false,
"message": "Unauthorized."
}
Submit a Bundles request to /bundles.
[]
{
"success": true,
"data": [
{
"id": 1,
"name": "Example"
}
]
}
{
"success": false,
"message": "Unauthorized."
}
Submit a Bundles request to /bundles/calculate.
[]
{
"success": true,
"data": {
"id": 1,
"name": "Example"
}
}
{
"success": false,
"message": "Unauthorized."
}
Delete Bundles data through /bundles/{bundle}.
| Parameter | In | Type | Description |
|---|---|---|---|
| bundle *required | path | string | Path parameter: Bundle |
[]
{
"success": true,
"message": "Operation completed successfully."
}
{
"success": false,
"message": "Unauthorized."
}
Retrieve Bundles data from /bundles/{bundle}.
| Parameter | In | Type | Description |
|---|---|---|---|
| bundle *required | path | string | Path parameter: Bundle |
[]
{
"success": true,
"data": {
"id": 1,
"name": "Example"
}
}
{
"success": false,
"message": "Unauthorized."
}
Update Bundles data through /bundles/{bundle}.
| Parameter | In | Type | Description |
|---|---|---|---|
| bundle *required | path | string | Path parameter: Bundle |
[]
{
"success": true,
"data": {
"id": 1,
"name": "Example"
}
}
{
"success": false,
"message": "Unauthorized."
}
Retrieve Bundles data from /bundles/{bundle}/availability.
| Parameter | In | Type | Description |
|---|---|---|---|
| bundle *required | path | string | Path parameter: Bundle |
[]
{
"success": true,
"data": {
"id": 1,
"name": "Example"
}
}
{
"success": false,
"message": "Unauthorized."
}
Retrieve Currencies data from /currencies.
[]
{
"success": true,
"data": [
{
"id": 1,
"name": "Example"
}
]
}
{
"success": false,
"message": "Unauthorized."
}
Retrieve Currencies data from /currencies/convert.
[]
{
"success": true,
"data": {
"id": 1,
"name": "Example"
}
}
{
"success": false,
"message": "Unauthorized."
}
Retrieve Currencies data from /currencies/{currency}.
| Parameter | In | Type | Description |
|---|---|---|---|
| currency *required | path | string | Path parameter: Currency |
[]
{
"success": true,
"data": {
"id": 1,
"name": "Example"
}
}
{
"success": false,
"message": "Unauthorized."
}
Get paginated pricing rules with optional store, validity, and relationship filters.
[]
{
"success": true,
"data": [
{
"id": 1,
"name": "Example"
}
]
}
{
"success": false,
"message": "Unauthorized."
}
Submit a Pricing Rules request to /pricing-rules.
[]
{
"success": true,
"data": [
{
"id": 1,
"name": "Example"
}
]
}
{
"success": false,
"message": "Unauthorized."
}
Submit a Pricing Rules request to /pricing-rules/evaluate.
[]
{
"success": true,
"data": {
"id": 1,
"name": "Example"
}
}
{
"success": false,
"message": "Unauthorized."
}
Delete Pricing Rules data through /pricing-rules/{pricingRule}.
| Parameter | In | Type | Description |
|---|---|---|---|
| pricingRule *required | path | string | Path parameter: Pricing Rule |
[]
{
"success": true,
"message": "Operation completed successfully."
}
{
"success": false,
"message": "Unauthorized."
}
Retrieve Pricing Rules data from /pricing-rules/{pricingRule}.
| Parameter | In | Type | Description |
|---|---|---|---|
| pricingRule *required | path | string | Path parameter: Pricing Rule |
[]
{
"success": true,
"data": {
"id": 1,
"name": "Example"
}
}
{
"success": false,
"message": "Unauthorized."
}
Update Pricing Rules data through /pricing-rules/{pricingRule}.
| Parameter | In | Type | Description |
|---|---|---|---|
| pricingRule *required | path | string | Path parameter: Pricing Rule |
[]
{
"success": true,
"data": {
"id": 1,
"name": "Example"
}
}
{
"success": false,
"message": "Unauthorized."
}
Submit a Pricing Rules request to /pricing-rules/{pricingRule}/toggle.
| Parameter | In | Type | Description |
|---|---|---|---|
| pricingRule *required | path | string | Path parameter: Pricing Rule |
[]
{
"success": true,
"data": {
"id": 1,
"name": "Example"
}
}
{
"success": false,
"message": "Unauthorized."
}
Evaluate pricing rules for a single item request.
[]
{
"success": true,
"data": {
"id": 1,
"name": "Example"
}
}
{
"success": false,
"message": "Unauthorized."
}
Submit a Pricing request to /pricing/calculate-result.
[]
{
"success": true,
"data": {
"id": 1,
"name": "Example"
}
}
{
"success": false,
"message": "Unauthorized."
}
Submit a Pricing request to /pricing/cart.
[]
{
"success": true,
"data": {
"id": 1,
"name": "Example"
}
}
{
"success": false,
"message": "Unauthorized."
}
Retrieve Pricing data from /pricing/rules.
[]
{
"success": true,
"data": [
{
"id": 1,
"name": "Example"
}
]
}
{
"success": false,
"message": "Unauthorized."
}
Retrieve Pricing data from /pricing/rules-for-item/{item}.
| Parameter | In | Type | Description |
|---|---|---|---|
| item *required | path | string | Path parameter: Item |
[]
{
"success": true,
"data": {
"id": 1,
"name": "Example"
}
}
{
"success": false,
"message": "Unauthorized."
}
Submit a Pricing request to /pricing/validate.
[]
{
"success": true,
"data": {
"id": 1,
"name": "Example"
}
}
{
"success": false,
"message": "Unauthorized."
}
Reporting
Read-only integration sales and report feeds for external consumers.
Retrieve Integration Reports data from /integration/reports/cashier.
[]
{
"success": true,
"data": {
"id": 1,
"name": "Example"
}
}
{
"success": false,
"message": "Unauthorized."
}
Retrieve Integration Reports Cashier data from /integration/reports/cashier/{cashier}.
| Parameter | In | Type | Description |
|---|---|---|---|
| cashier *required | path | string | Path parameter: Cashier |
[]
{
"success": true,
"data": {
"id": 1,
"name": "Example"
}
}
{
"success": false,
"message": "Unauthorized."
}
Retrieve Integration Reports data from /integration/reports/closeout.
[]
{
"success": true,
"data": {
"id": 1,
"name": "Example"
}
}
{
"success": false,
"message": "Unauthorized."
}
Retrieve Integration Reports data from /integration/reports/customers.
[]
{
"success": true,
"data": [
{
"id": 1,
"name": "Example"
}
]
}
{
"success": false,
"message": "Unauthorized."
}
Retrieve Integration Reports data from /integration/reports/discounts.
[]
{
"success": true,
"data": [
{
"id": 1,
"name": "Example"
}
]
}
{
"success": false,
"message": "Unauthorized."
}
Retrieve Integration Reports data from /integration/reports/heatmap.
[]
{
"success": true,
"data": {
"id": 1,
"name": "Example"
}
}
{
"success": false,
"message": "Unauthorized."
}
Retrieve Integration Reports Inventory data from /integration/reports/inventory/damage-spoilage.
[]
{
"success": true,
"data": {
"id": 1,
"name": "Example"
}
}
{
"success": false,
"message": "Unauthorized."
}
Retrieve Integration Reports Inventory data from /integration/reports/inventory/dead-stock.
[]
{
"success": true,
"data": {
"id": 1,
"name": "Example"
}
}
{
"success": false,
"message": "Unauthorized."
}
Retrieve Integration Reports Inventory data from /integration/reports/inventory/movement.
[]
{
"success": true,
"data": {
"id": 1,
"name": "Example"
}
}
{
"success": false,
"message": "Unauthorized."
}
Retrieve Integration Reports Inventory data from /integration/reports/inventory/overview.
[]
{
"success": true,
"data": {
"id": 1,
"name": "Example"
}
}
{
"success": false,
"message": "Unauthorized."
}
Retrieve Integration Reports Inventory data from /integration/reports/inventory/replenishment.
[]
{
"success": true,
"data": {
"id": 1,
"name": "Example"
}
}
{
"success": false,
"message": "Unauthorized."
}
Retrieve Integration Reports Inventory data from /integration/reports/inventory/stock.
[]
{
"success": true,
"data": {
"id": 1,
"name": "Example"
}
}
{
"success": false,
"message": "Unauthorized."
}
Retrieve Integration Reports Inventory data from /integration/reports/inventory/turns.
[]
{
"success": true,
"data": [
{
"id": 1,
"name": "Example"
}
]
}
{
"success": false,
"message": "Unauthorized."
}
Retrieve Integration Reports Inventory data from /integration/reports/inventory/valuation.
[]
{
"success": true,
"data": {
"id": 1,
"name": "Example"
}
}
{
"success": false,
"message": "Unauthorized."
}
Retrieve Integration Reports data from /integration/reports/invoices.
[]
{
"success": true,
"data": [
{
"id": 1,
"name": "Example"
}
]
}
{
"success": false,
"message": "Unauthorized."
}
Retrieve Integration Reports Invoices data from /integration/reports/invoices/aging.
[]
{
"success": true,
"data": {
"id": 1,
"name": "Example"
}
}
{
"success": false,
"message": "Unauthorized."
}
Retrieve Integration Reports Invoices data from /integration/reports/invoices/credit-notes.
[]
{
"success": true,
"data": [
{
"id": 1,
"name": "Example"
}
]
}
{
"success": false,
"message": "Unauthorized."
}
Retrieve Integration Reports Invoices data from /integration/reports/invoices/dso.
[]
{
"success": true,
"data": {
"id": 1,
"name": "Example"
}
}
{
"success": false,
"message": "Unauthorized."
}
Retrieve Integration Reports Invoices data from /integration/reports/invoices/funnel.
[]
{
"success": true,
"data": {
"id": 1,
"name": "Example"
}
}
{
"success": false,
"message": "Unauthorized."
}
Return high-level report overview data for external consumers.
[]
{
"success": true,
"data": {
"id": 1,
"name": "Example"
}
}
{
"success": false,
"message": "Unauthorized."
}
Retrieve Integration Reports data from /integration/reports/payments.
[]
{
"success": true,
"data": [
{
"id": 1,
"name": "Example"
}
]
}
{
"success": false,
"message": "Unauthorized."
}
Retrieve Integration Reports data from /integration/reports/price-source.
[]
{
"success": true,
"data": {
"id": 1,
"name": "Example"
}
}
{
"success": false,
"message": "Unauthorized."
}
Retrieve Integration Reports data from /integration/reports/products.
[]
{
"success": true,
"data": [
{
"id": 1,
"name": "Example"
}
]
}
{
"success": false,
"message": "Unauthorized."
}
Retrieve Integration Reports Products data from /integration/reports/products/abc.
[]
{
"success": true,
"data": {
"id": 1,
"name": "Example"
}
}
{
"success": false,
"message": "Unauthorized."
}
Retrieve Integration Reports data from /integration/reports/profit.
[]
{
"success": true,
"data": {
"id": 1,
"name": "Example"
}
}
{
"success": false,
"message": "Unauthorized."
}
Retrieve Integration Reports data from /integration/reports/return-rate.
[]
{
"success": true,
"data": {
"id": 1,
"name": "Example"
}
}
{
"success": false,
"message": "Unauthorized."
}
Retrieve Integration Reports data from /integration/reports/returns.
[]
{
"success": true,
"data": [
{
"id": 1,
"name": "Example"
}
]
}
{
"success": false,
"message": "Unauthorized."
}
Retrieve Integration Reports data from /integration/reports/revenue.
[]
{
"success": true,
"data": {
"id": 1,
"name": "Example"
}
}
{
"success": false,
"message": "Unauthorized."
}
Return detailed sales reporting rows for external consumers.
[]
{
"success": true,
"data": [
{
"id": 1,
"name": "Example"
}
]
}
{
"success": false,
"message": "Unauthorized."
}
Retrieve Integration Reports data from /integration/reports/stores.
[]
{
"success": true,
"data": [
{
"id": 1,
"name": "Example"
}
]
}
{
"success": false,
"message": "Unauthorized."
}
Retrieve Integration Reports data from /integration/reports/tax.
[]
{
"success": true,
"data": {
"id": 1,
"name": "Example"
}
}
{
"success": false,
"message": "Unauthorized."
}
Retrieve Integration Reports data from /integration/reports/voids.
[]
{
"success": true,
"data": [
{
"id": 1,
"name": "Example"
}
]
}
{
"success": false,
"message": "Unauthorized."
}
Pull sales for reporting and aggregation. Compatibility alias: `/integrations/sales`. Common scenarios include Store Rollup Pull and Warehouse Rollup Pull using `store_id` and `warehouse_id` filters.
| Parameter | In | Type | Description |
|---|---|---|---|
| from | query | date | Start date (YYYY-MM-DD) |
| to | query | date | End date (YYYY-MM-DD) |
| status | query | string | Sale status filter |
| store_id | query | integer | Optional store filter |
| warehouse_id | query | integer | Optional warehouse filter |
| q | query | string | Free-text search on receipt, barcode, or reference |
| per_page | query | integer | Results per page (default: 50, max: 200) |
{
"from": "2026-02-11",
"to": "2026-02-11",
"status": "completed",
"store_id": 1,
"per_page": 100
}
{
"success": true,
"data": [
{
"id": 901,
"receipt_number": "RCP-20260211-0001",
"status": "completed",
"store": {
"id": 1,
"name": "Main Store"
},
"warehouse": {
"id": 2,
"name": "Sales Floor"
},
"total_amount": 245,
"created_at": "2026-02-11T09: 15: 00Z"
}
],
"meta": {
"current_page": 1,
"last_page": 3,
"per_page": 100,
"total": 467
}
}
{
"success": false,
"message": "Unauthorized."
}
Use sale detail lookups to reconcile line items and payments flagged during reporting syncs. Compatibility alias: `/integrations/sales/{sale}`.
| Parameter | In | Type | Description |
|---|---|---|---|
| sale *required | path | string | Path parameter: Sale |
[]
{
"success": true,
"data": {
"id": 1,
"name": "Example"
}
}
{
"success": false,
"message": "Unauthorized."
}
Integration Data
Read-only integration access to items, customers, stores, warehouses, and related data feeds.
Return paginated customer data for external integrations.
[]
{
"success": true,
"data": [
{
"id": 1,
"name": "Example"
}
]
}
{
"success": false,
"message": "Unauthorized."
}
Retrieve Integration Data Customers data from /integration/data/customers/{customer}.
| Parameter | In | Type | Description |
|---|---|---|---|
| customer *required | path | string | Path parameter: Customer |
[]
{
"success": true,
"data": {
"id": 1,
"name": "Example"
}
}
{
"success": false,
"message": "Unauthorized."
}
Retrieve Integration Data Inventory data from /integration/data/inventory.
[]
{
"success": true,
"data": [
{
"id": 1,
"name": "Example"
}
]
}
{
"success": false,
"message": "Unauthorized."
}
Retrieve Integration Data Invoices data from /integration/data/invoices.
[]
{
"success": true,
"data": [
{
"id": 1,
"name": "Example"
}
]
}
{
"success": false,
"message": "Unauthorized."
}
Retrieve Integration Data Invoices data from /integration/data/invoices/{invoice}.
| Parameter | In | Type | Description |
|---|---|---|---|
| invoice *required | path | string | Path parameter: Invoice |
[]
{
"success": true,
"data": {
"id": 1,
"name": "Example"
}
}
{
"success": false,
"message": "Unauthorized."
}
Return paginated item data for external integrations.
[]
{
"success": true,
"data": [
{
"id": 1,
"name": "Example"
}
]
}
{
"success": false,
"message": "Unauthorized."
}
Retrieve Integration Data Returns data from /integration/data/returns.
[]
{
"success": true,
"data": [
{
"id": 1,
"name": "Example"
}
]
}
{
"success": false,
"message": "Unauthorized."
}
Retrieve Integration Data Returns data from /integration/data/returns/{saleReturn}.
| Parameter | In | Type | Description |
|---|---|---|---|
| saleReturn *required | path | string | Path parameter: Sale Return |
[]
{
"success": true,
"data": {
"id": 1,
"name": "Example"
}
}
{
"success": false,
"message": "Unauthorized."
}
Retrieve Integration Data Sales data from /integration/data/sales.
[]
{
"success": true,
"data": [
{
"id": 1,
"name": "Example"
}
]
}
{
"success": false,
"message": "Unauthorized."
}
Retrieve Integration Data Sales data from /integration/data/sales/{sale}.
| Parameter | In | Type | Description |
|---|---|---|---|
| sale *required | path | string | Path parameter: Sale |
[]
{
"success": true,
"data": {
"id": 1,
"name": "Example"
}
}
{
"success": false,
"message": "Unauthorized."
}
Return store reference data for external integrations.
[]
{
"success": true,
"data": [
{
"id": 1,
"name": "Example"
}
]
}
{
"success": false,
"message": "Unauthorized."
}
Retrieve Integration Data Stores data from /integration/data/stores/{store}.
| Parameter | In | Type | Description |
|---|---|---|---|
| store *required | path | string | Path parameter: Store |
[]
{
"success": true,
"data": {
"id": 1,
"name": "Example"
}
}
{
"success": false,
"message": "Unauthorized."
}
Return warehouse reference data for external integrations.
[]
{
"success": true,
"data": [
{
"id": 1,
"name": "Example"
}
]
}
{
"success": false,
"message": "Unauthorized."
}
Retrieve Integration Data Warehouses data from /integration/data/warehouses/{warehouse}.
| Parameter | In | Type | Description |
|---|---|---|---|
| warehouse *required | path | string | Path parameter: Warehouse |
[]
{
"success": true,
"data": {
"id": 1,
"name": "Example"
}
}
{
"success": false,
"message": "Unauthorized."
}
Other
Additional authenticated API endpoints.
Submit a Prices request to /prices/bulk.
[]
{
"success": true,
"data": {
"id": 1,
"name": "Example"
}
}
{
"success": false,
"message": "Unauthorized."
}
Error Handling
HTTP Status Codes
The API uses standard HTTP status codes to indicate the success or failure of requests:
| Status | Meaning | Description |
|---|---|---|
| 200 | OK | Request succeeded |
| 201 | Created | Resource created successfully |
| 400 | Bad Request | Invalid request data or checkout validation error |
| 401 | Unauthorized | Missing or invalid authentication token |
| 403 | Forbidden | User lacks permission for this action |
| 404 | Not Found | Resource not found |
| 409 | Conflict | Conflict (e.g., duplicate session) |
| 422 | Unprocessable Entity | Validation errors in request data |
| 429 | Too Many Requests | Rate limit exceeded |
| 500 | Server Error | Internal server error |
Validation Error Response
When validation fails (422), the response includes field-specific errors:
{
"success": false,
"message": "The given data was invalid.",
"errors": {
"email": ["The email field is required."],
"password": ["The password must be at least 8 characters."]
}
}