NakPOS Documentation

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 Response
{
    "success": true,
    "data": { "...response data..." },
    "meta": { "...pagination info (if applicable)..." }
}
Error Response
{
    "success": false,
    "message": "Error description",
    "errors": { "...validation errors (if applicable)..." }
}

Staff Mobile

Private selected-location staff orders, settlement, and finalized receipts.

GET /staff/auth/devices List Staff Auth Devices
Auth 60/minute

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.

Example Request
JSON
[]
Example Success Response
JSON
{
    "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
            }
        ]
    }
}
Example Error Response
JSON
{
    "success": false,
    "code": "validation_failed",
    "message": "The given data was invalid.",
    "errors": {
        "store_id": [
            "Select an accessible active store."
        ]
    }
}
PATCH /staff/auth/devices/current Get Current Staff Auth Devices
Auth 60/minute

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.

Request Parameters
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.
Example Request
JSON
{
    "device_mode": "example",
    "biometric_enabled": true
}
Example Success Response
JSON
{
    "success": true,
    "data": {
        "device_session": {
            "id": "00000000-0000-4000-8000-000000000001",
            "device_mode": "shared",
            "biometric_enabled": false
        }
    }
}
Example Error Response
JSON
{
    "success": false,
    "code": "validation_failed",
    "message": "The given data was invalid.",
    "errors": {
        "store_id": [
            "Select an accessible active store."
        ]
    }
}
DELETE /staff/auth/devices/{session} Delete Staff Auth Devices
Auth 60/minute

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.

Request Parameters
Parameter In Type Description
session *required path string Owned device-session UUID; foreign ownership returns 404.
Example Request
JSON
[]
Example Success Response
JSON
{
    "success": true,
    "data": {
        "message": "Device signed out."
    }
}
Example Error Response
JSON
{
    "success": false,
    "code": "validation_failed",
    "message": "The given data was invalid.",
    "errors": {
        "store_id": [
            "Select an accessible active store."
        ]
    }
}
POST /staff/auth/login Login
5/minute

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.

Request Parameters
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.
Example Request
JSON
{
    "email": "staff@example.invalid",
    "password": "synthetic-password",
    "device_name": "Example Device"
}
Example Success Response
JSON
{
    "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"
        ]
    }
}
Example Error Response
JSON
{
    "success": false,
    "code": "validation_failed",
    "message": "The given data was invalid.",
    "errors": {
        "email": [
            "The provided credentials are incorrect."
        ]
    }
}
POST /staff/auth/logout Logout
Auth 60/minute

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.

Example Request
JSON
[]
Example Success Response
JSON
{
    "success": true,
    "data": {
        "message": "Successfully logged out."
    }
}
Example Error Response
JSON
{
    "success": false,
    "code": "validation_failed",
    "message": "The given data was invalid.",
    "errors": {
        "store_id": [
            "Select an accessible active store."
        ]
    }
}
POST /staff/auth/logout-all Logout All
Auth 60/minute

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.

Example Request
JSON
[]
Example Success Response
JSON
{
    "success": true,
    "data": {
        "message": "All trusted devices signed out."
    }
}
Example Error Response
JSON
{
    "success": false,
    "code": "validation_failed",
    "message": "The given data was invalid.",
    "errors": {
        "store_id": [
            "Select an accessible active store."
        ]
    }
}
POST /staff/auth/password/forgot Forgot

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.

Request Parameters
Parameter In Type Description
email *required body string Work email.
Example Request
JSON
{
    "email": "staff@example.invalid"
}
Example Success Response
JSON
{
    "success": true,
    "data": {
        "message": "If that address has a staff account, a reset link is on its way. It works for 1 hour."
    }
}
Example Error Response
JSON
{
    "success": false,
    "code": "validation_failed",
    "message": "The given data was invalid.",
    "errors": {
        "store_id": [
            "Select an accessible active store."
        ]
    }
}
POST /staff/auth/pin Pin
Auth 60/minute

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.

Request Parameters
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.
Example Request
JSON
{
    "pin": "example",
    "current_pin": "example"
}
Example Success Response
JSON
{
    "success": true,
    "data": {
        "has_pin": true,
        "pin_set_at": "2026-10-04T12: 00: 00+00: 00"
    }
}
Example Error Response
JSON
{
    "success": false,
    "code": "validation_failed",
    "message": "The given data was invalid.",
    "errors": {
        "store_id": [
            "Select an accessible active store."
        ]
    }
}
POST /staff/auth/pin-unlock Pin Unlock

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.

Request Parameters
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.
Example Request
JSON
{
    "session_id": "00000000-0000-4000-8000-000000000001",
    "pin_secret": "SYNTHETIC-NONFUNCTIONAL-PIN-SECRET",
    "pin": "000000"
}
Example Success Response
JSON
{
    "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
        }
    }
}
Example Error Response
JSON
{
    "success": false,
    "code": "validation_failed",
    "message": "The given data was invalid.",
    "errors": {
        "store_id": [
            "Select an accessible active store."
        ]
    }
}
POST /staff/auth/refresh Refresh

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.

Request Parameters
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.
Example Request
JSON
{
    "session_id": "example",
    "refresh_token": "example",
    "request_id": "example"
}
Example Success Response
JSON
{
    "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"
        }
    }
}
Example Error Response
JSON
{
    "success": false,
    "code": "validation_failed",
    "message": "The given data was invalid.",
    "errors": {
        "store_id": [
            "Select an accessible active store."
        ]
    }
}
GET /staff/auth/sign-ins Sign Ins
Auth 60/minute

The person's 20 most recent sign-ins and unlocks (password, biometric or pin), newest first. IP addresses are never returned. Private, no-store.

Example Request
JSON
[]
Example Success Response
JSON
{
    "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"
            }
        ]
    }
}
Example Error Response
JSON
{
    "success": false,
    "code": "validation_failed",
    "message": "The given data was invalid.",
    "errors": {
        "store_id": [
            "Select an accessible active store."
        ]
    }
}
POST /staff/auth/step-up Step Up
Auth 60/minute

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.

Request Parameters
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.
Example Request
JSON
{
    "action": "example",
    "subject_id": "example",
    "method": "example",
    "pin": "example",
    "session_id": "example",
    "refresh_token": "example",
    "request_id": "example"
}
Example Success Response
JSON
{
    "success": true,
    "data": {
        "step_up_token": "SYNTHETIC-NONFUNCTIONAL-STEP-UP",
        "expires_at": "2026-10-04T12: 02: 00+00: 00"
    }
}
Example Error Response
JSON
{
    "success": false,
    "code": "validation_failed",
    "message": "The given data was invalid.",
    "errors": {
        "store_id": [
            "Select an accessible active store."
        ]
    }
}
GET /staff/bootstrap Bootstrap
Auth 60/minute

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.

Example Request
JSON
[]
Example Success Response
JSON
{
    "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
                    }
                ]
            }
        ]
    }
}
Example Error Response
JSON
{
    "success": false,
    "code": "validation_failed",
    "message": "The given data was invalid.",
    "errors": {
        "store_id": [
            "Select an accessible active store."
        ]
    }
}
GET /staff/cashier-sessions List Staff Cashier Sessions
Auth 60/minute

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.

Request Parameters
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.
Example Request
JSON
{
    "store_id": 1,
    "warehouse_id": 1,
    "page": 1,
    "per_page": 1
}
Example Success Response
JSON
{
    "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
    }
}
Example Error Response
JSON
{
    "success": false,
    "code": "validation_failed",
    "message": "The given data was invalid.",
    "errors": {
        "store_id": [
            "Select an accessible active store."
        ]
    }
}
POST /staff/cashier-sessions Open
Auth 60/minute

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.

Request Parameters
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.
Example Request
JSON
{
    "store_id": 1,
    "warehouse_id": 1,
    "operation_key": "00000000-0000-4000-8000-000000000001",
    "opening_balance": "100.00"
}
Example Success Response
JSON
{
    "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
            }
        }
    }
}
Example Error Response
JSON
{
    "success": false,
    "code": "validation_failed",
    "message": "The given data was invalid.",
    "errors": {
        "store_id": [
            "Select an accessible active store."
        ]
    }
}
GET /staff/cashier-sessions/current Get Current Staff Cashier Sessions
Auth 60/minute

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.

Request Parameters
Parameter In Type Description
store_id *required query integer Selected accessible active store.
warehouse_id query integer Optional selected accessible active warehouse.
Example Request
JSON
{
    "store_id": 1,
    "warehouse_id": 1
}
Example Success Response
JSON
{
    "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
            }
        }
    }
}
Example Error Response
JSON
{
    "success": false,
    "code": "validation_failed",
    "message": "The given data was invalid.",
    "errors": {
        "store_id": [
            "Select an accessible active store."
        ]
    }
}
GET /staff/cashier-sessions/{session} Get Staff Cashier Sessions
Auth 60/minute

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.

Request Parameters
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.
Example Request
JSON
{
    "store_id": 1,
    "warehouse_id": 1
}
Example Success Response
JSON
{
    "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
            }
        }
    }
}
Example Error Response
JSON
{
    "success": false,
    "code": "validation_failed",
    "message": "The given data was invalid.",
    "errors": {
        "store_id": [
            "Select an accessible active store."
        ]
    }
}
POST /staff/cashier-sessions/{session}/close Close
Auth 60/minute

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.

Request Parameters
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.
Example Request
JSON
{
    "store_id": 1,
    "warehouse_id": 1,
    "operation_key": "00000000-0000-4000-8000-000000000001",
    "revision": "opaque-example-revision",
    "closing_balance": "100.00"
}
Example Success Response
JSON
{
    "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
            }
        }
    }
}
Example Error Response
JSON
{
    "success": false,
    "code": "validation_failed",
    "message": "The given data was invalid.",
    "errors": {
        "store_id": [
            "Select an accessible active store."
        ]
    }
}
POST /staff/cashier-sessions/{session}/movements Movements
Auth 60/minute

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.

Request Parameters
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.
Example Request
JSON
{
    "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"
}
Example Success Response
JSON
{
    "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
            }
        }
    }
}
Example Error Response
JSON
{
    "success": false,
    "code": "validation_failed",
    "message": "The given data was invalid.",
    "errors": {
        "store_id": [
            "Select an accessible active store."
        ]
    }
}
POST /staff/cashier-sessions/{session}/resume Resume
Auth 60/minute

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.

Request Parameters
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.
Example Request
JSON
{
    "store_id": 1,
    "warehouse_id": 1,
    "operation_key": "00000000-0000-4000-8000-000000000001",
    "revision": "opaque-example-revision"
}
Example Success Response
JSON
{
    "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
            }
        }
    }
}
Example Error Response
JSON
{
    "success": false,
    "code": "validation_failed",
    "message": "The given data was invalid.",
    "errors": {
        "store_id": [
            "Select an accessible active store."
        ]
    }
}
GET /staff/cashier-sessions/{session}/summary Staff Cashier Sessions Summary
Auth 60/minute

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.

Request Parameters
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.
Example Request
JSON
{
    "store_id": 1,
    "warehouse_id": 1
}
Example Success Response
JSON
{
    "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"
    }
}
Example Error Response
JSON
{
    "success": false,
    "code": "validation_failed",
    "message": "The given data was invalid.",
    "errors": {
        "store_id": [
            "Select an accessible active store."
        ]
    }
}
POST /staff/cashier-sessions/{session}/suspend Suspend
Auth 60/minute

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.

Request Parameters
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.
Example Request
JSON
{
    "store_id": 1,
    "warehouse_id": 1,
    "operation_key": "00000000-0000-4000-8000-000000000001",
    "revision": "opaque-example-revision"
}
Example Success Response
JSON
{
    "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
            }
        }
    }
}
Example Error Response
JSON
{
    "success": false,
    "code": "validation_failed",
    "message": "The given data was invalid.",
    "errors": {
        "store_id": [
            "Select an accessible active store."
        ]
    }
}
GET /staff/credit-notes List Staff Credit Notes
Auth 60/minute

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.

Request Parameters
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.
Example Request
JSON
{
    "store_id": 1,
    "warehouse_id": 1,
    "q": "example",
    "page": 1,
    "per_page": 1,
    "invoice_id": 1,
    "status": "example"
}
Example Success Response
JSON
{
    "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
    }
}
Example Error Response
JSON
{
    "success": false,
    "code": "validation_failed",
    "message": "The given data was invalid.",
    "errors": {
        "store_id": [
            "Select an accessible active store."
        ]
    }
}
GET /staff/credit-notes/{creditNote} Get Staff Credit Notes
Auth 60/minute

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.

Request Parameters
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.
Example Request
JSON
{
    "store_id": 1,
    "warehouse_id": 1
}
Example Success Response
JSON
{
    "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
    }
}
Example Error Response
JSON
{
    "success": false,
    "code": "validation_failed",
    "message": "The given data was invalid.",
    "errors": {
        "store_id": [
            "Select an accessible active store."
        ]
    }
}
POST /staff/credit-notes/{creditNote}/apply Apply
Auth 60/minute

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.

Request Parameters
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.
Example Request
JSON
{
    "store_id": 1,
    "warehouse_id": 1,
    "operation_key": "00000000-0000-4000-8000-000000000001",
    "revision": "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa",
    "amount": "12.50",
    "invoice_id": 1
}
Example Success Response
JSON
{
    "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
        }
    }
}
Example Error Response
JSON
{
    "success": false,
    "code": "validation_failed",
    "message": "The given data was invalid.",
    "errors": {
        "store_id": [
            "Select an accessible active store."
        ]
    }
}
POST /staff/credit-notes/{creditNote}/approve Approve
Auth 60/minute

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.

Request Parameters
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.
Example Request
JSON
{
    "store_id": 1,
    "warehouse_id": 1,
    "operation_key": "00000000-0000-4000-8000-000000000001",
    "revision": "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"
}
Example Success Response
JSON
{
    "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
        }
    }
}
Example Error Response
JSON
{
    "success": false,
    "code": "validation_failed",
    "message": "The given data was invalid.",
    "errors": {
        "store_id": [
            "Select an accessible active store."
        ]
    }
}
GET /staff/credit-notes/{creditNote}/eligible-invoices Targets
Auth 60/minute

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.

Request Parameters
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.
Example Request
JSON
{
    "store_id": 1,
    "warehouse_id": 1,
    "q": "example",
    "page": 1,
    "per_page": 1
}
Example Success Response
JSON
{
    "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
    }
}
Example Error Response
JSON
{
    "success": false,
    "code": "validation_failed",
    "message": "The given data was invalid.",
    "errors": {
        "store_id": [
            "Select an accessible active store."
        ]
    }
}
POST /staff/credit-notes/{creditNote}/refund Refund
Auth 60/minute

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.

Request Parameters
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.
Example Request
JSON
{
    "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
}
Example Success Response
JSON
{
    "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
        }
    }
}
Example Error Response
JSON
{
    "success": false,
    "code": "validation_failed",
    "message": "The given data was invalid.",
    "errors": {
        "store_id": [
            "Select an accessible active store."
        ]
    }
}
POST /staff/credit-notes/{creditNote}/reject Reject
Auth 60/minute

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.

Request Parameters
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.
Example Request
JSON
{
    "store_id": 1,
    "warehouse_id": 1,
    "operation_key": "00000000-0000-4000-8000-000000000001",
    "revision": "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa",
    "reason": "Example reason"
}
Example Success Response
JSON
{
    "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
        }
    }
}
Example Error Response
JSON
{
    "success": false,
    "code": "validation_failed",
    "message": "The given data was invalid.",
    "errors": {
        "store_id": [
            "Select an accessible active store."
        ]
    }
}
POST /staff/credit-notes/{creditNote}/submit Submit
Auth 60/minute

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.

Request Parameters
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.
Example Request
JSON
{
    "store_id": 1,
    "warehouse_id": 1,
    "operation_key": "00000000-0000-4000-8000-000000000001",
    "revision": "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"
}
Example Success Response
JSON
{
    "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
        }
    }
}
Example Error Response
JSON
{
    "success": false,
    "code": "validation_failed",
    "message": "The given data was invalid.",
    "errors": {
        "store_id": [
            "Select an accessible active store."
        ]
    }
}
POST /staff/credit-notes/{creditNote}/void Void
Auth 60/minute

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.

Request Parameters
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.
Example Request
JSON
{
    "store_id": 1,
    "warehouse_id": 1,
    "operation_key": "00000000-0000-4000-8000-000000000001",
    "revision": "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa",
    "reason": "Example reason"
}
Example Success Response
JSON
{
    "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
        }
    }
}
Example Error Response
JSON
{
    "success": false,
    "code": "validation_failed",
    "message": "The given data was invalid.",
    "errors": {
        "store_id": [
            "Select an accessible active store."
        ]
    }
}
GET /staff/customers List Staff Customers
Auth 60/minute

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.

Request Parameters
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.
Example Request
JSON
{
    "q": "example",
    "page": 1,
    "per_page": 1
}
Example Success Response
JSON
{
    "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
    }
}
Example Error Response
JSON
{
    "success": false,
    "code": "validation_failed",
    "message": "The given data was invalid.",
    "errors": {
        "store_id": [
            "Select an accessible active store."
        ]
    }
}
POST /staff/customers Create Staff Customers
Auth 60/minute

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.

Request Parameters
Parameter In Type Description
name *required body string Contact name, maximum 255 characters.
email 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.
Example Request
JSON
{
    "store_id": 1,
    "workflow": "pos",
    "operation_key": "00000000-0000-4000-8000-000000000001",
    "name": "Example Customer"
}
Example Success Response
JSON
{
    "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
        }
    }
}
Example Error Response
JSON
{
    "success": false,
    "code": "validation_failed",
    "message": "The given data was invalid.",
    "errors": {
        "store_id": [
            "Select an accessible active store."
        ]
    }
}
DELETE /staff/customers/{customer} Delete Staff Customers
Auth 60/minute

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.

Request Parameters
Parameter In Type Description
customer *required path string Path parameter: Customer
revision *required body string Fresh opaque customer revision, maximum 255 characters.
Example Request
JSON
{
    "revision": "example"
}
Example Success Response
JSON
{
    "success": true,
    "data": {
        "deleted": true
    }
}
Example Error Response
JSON
{
    "success": false,
    "code": "validation_failed",
    "message": "The given data was invalid.",
    "errors": {
        "store_id": [
            "Select an accessible active store."
        ]
    }
}
GET /staff/customers/{customer} Get Staff Customers
Auth 60/minute

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.

Request Parameters
Parameter In Type Description
customer *required path string Path parameter: Customer
Example Request
JSON
[]
Example Success Response
JSON
{
    "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
    }
}
Example Error Response
JSON
{
    "success": false,
    "code": "validation_failed",
    "message": "The given data was invalid.",
    "errors": {
        "store_id": [
            "Select an accessible active store."
        ]
    }
}
PATCH /staff/customers/{customer} Update Staff Customers
Auth 60/minute

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.

Request Parameters
Parameter In Type Description
customer *required path string Path parameter: Customer
name body string Contact name, maximum 255 characters.
email 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.
Example Request
JSON
{
    "store_id": 1,
    "workflow": "pos",
    "operation_key": "00000000-0000-4000-8000-000000000001",
    "name": "Example Customer",
    "revision": "opaque-example-revision"
}
Example Success Response
JSON
{
    "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
        }
    }
}
Example Error Response
JSON
{
    "success": false,
    "code": "validation_failed",
    "message": "The given data was invalid.",
    "errors": {
        "store_id": [
            "Select an accessible active store."
        ]
    }
}
GET /staff/customers/{customer}/history History
Auth 60/minute

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.

Request Parameters
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.
Example Request
JSON
{
    "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
}
Example Success Response
JSON
{
    "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
    }
}
Example Error Response
JSON
{
    "success": false,
    "code": "validation_failed",
    "message": "The given data was invalid.",
    "errors": {
        "store_id": [
            "Select an accessible active store."
        ]
    }
}
POST /staff/documents/estimates/{estimate}/email Email
Auth 60/minute

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.

Request Parameters
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.
Example Request
JSON
{
    "store_id": 1,
    "warehouse_id": 1,
    "recipient": "customer@example.invalid",
    "operation_key": "00000000-0000-4000-8000-000000000001"
}
Example Success Response
JSON
{
    "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
        }
    }
}
Example Error Response
JSON
{
    "success": false,
    "code": "validation_failed",
    "message": "The given data was invalid.",
    "errors": {
        "store_id": [
            "Select an accessible active store."
        ]
    }
}
GET /staff/documents/estimates/{estimate}/pdf Pdf
Auth 60/minute

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.

Request Parameters
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.
Example Request
JSON
{
    "store_id": 1,
    "warehouse_id": 1
}
Example Success Response
JSON
"%PDF-1.7 (synthetic binary example)"
Example Error Response
JSON
{
    "success": false,
    "code": "validation_failed",
    "message": "The given data was invalid.",
    "errors": {
        "store_id": [
            "Select an accessible active store."
        ]
    }
}
POST /staff/documents/invoices/{invoice}/email Email
Auth 60/minute

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.

Request Parameters
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.
Example Request
JSON
{
    "store_id": 1,
    "warehouse_id": 1,
    "recipient": "customer@example.invalid",
    "operation_key": "00000000-0000-4000-8000-000000000001"
}
Example Success Response
JSON
{
    "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
        }
    }
}
Example Error Response
JSON
{
    "success": false,
    "code": "validation_failed",
    "message": "The given data was invalid.",
    "errors": {
        "store_id": [
            "Select an accessible active store."
        ]
    }
}
POST /staff/documents/invoices/{invoice}/payments/{payment}/email Email
Auth 60/minute

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.

Request Parameters
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.
Example Request
JSON
{
    "store_id": 1,
    "warehouse_id": 1,
    "recipient": "customer@example.invalid",
    "operation_key": "00000000-0000-4000-8000-000000000001"
}
Example Success Response
JSON
{
    "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
        }
    }
}
Example Error Response
JSON
{
    "success": false,
    "code": "validation_failed",
    "message": "The given data was invalid.",
    "errors": {
        "store_id": [
            "Select an accessible active store."
        ]
    }
}
GET /staff/documents/invoices/{invoice}/payments/{payment}/pdf Pdf
Auth 60/minute

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.

Request Parameters
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.
Example Request
JSON
{
    "store_id": 1,
    "warehouse_id": 1
}
Example Success Response
JSON
"%PDF-1.7 (synthetic binary example)"
Example Error Response
JSON
{
    "success": false,
    "code": "validation_failed",
    "message": "The given data was invalid.",
    "errors": {
        "store_id": [
            "Select an accessible active store."
        ]
    }
}
GET /staff/documents/invoices/{invoice}/pdf Pdf
Auth 60/minute

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.

Request Parameters
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.
Example Request
JSON
{
    "store_id": 1,
    "warehouse_id": 1
}
Example Success Response
JSON
"%PDF-1.7 (synthetic binary example)"
Example Error Response
JSON
{
    "success": false,
    "code": "validation_failed",
    "message": "The given data was invalid.",
    "errors": {
        "store_id": [
            "Select an accessible active store."
        ]
    }
}
GET /staff/documents/sales/{sale}/deliveries Deliveries
Auth 60/minute

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.

Request Parameters
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.
Example Request
JSON
{
    "store_id": 1,
    "warehouse_id": 1
}
Example Success Response
JSON
{
    "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
            }
        ]
    }
}
Example Error Response
JSON
{
    "success": false,
    "code": "validation_failed",
    "message": "The given data was invalid.",
    "errors": {
        "store_id": [
            "Select an accessible active store."
        ]
    }
}
POST /staff/documents/sales/{sale}/email Email
Auth 60/minute

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.

Request Parameters
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.
Example Request
JSON
{
    "store_id": 1,
    "warehouse_id": 1,
    "recipient": "customer@example.invalid",
    "operation_key": "00000000-0000-4000-8000-000000000001"
}
Example Success Response
JSON
{
    "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
        }
    }
}
Example Error Response
JSON
{
    "success": false,
    "code": "validation_failed",
    "message": "The given data was invalid.",
    "errors": {
        "store_id": [
            "Select an accessible active store."
        ]
    }
}
GET /staff/documents/sales/{sale}/pdf Pdf
Auth 60/minute

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.

Request Parameters
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.
Example Request
JSON
{
    "store_id": 1,
    "warehouse_id": 1
}
Example Success Response
JSON
"%PDF-1.7 (synthetic binary example)"
Example Error Response
JSON
{
    "success": false,
    "code": "validation_failed",
    "message": "The given data was invalid.",
    "errors": {
        "store_id": [
            "Select an accessible active store."
        ]
    }
}
GET /staff/estimates List Staff Estimates
Auth 60/minute

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.

Request Parameters
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.
Example Request
JSON
{
    "store_id": 1,
    "warehouse_id": 1,
    "q": "example",
    "page": 1,
    "per_page": 1,
    "status": "example",
    "date_from": "example",
    "date_to": "example"
}
Example Success Response
JSON
{
    "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
    }
}
Example Error Response
JSON
{
    "success": false,
    "code": "validation_failed",
    "message": "The given data was invalid.",
    "errors": {
        "store_id": [
            "Select an accessible active store."
        ]
    }
}
POST /staff/estimates Create Staff Estimates
Auth 60/minute

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.

Request Parameters
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.
Example Request
JSON
{
    "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"
}
Example Success Response
JSON
{
    "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
        }
    }
}
Example Error Response
JSON
{
    "success": false,
    "code": "validation_failed",
    "message": "The given data was invalid.",
    "errors": {
        "store_id": [
            "Select an accessible active store."
        ]
    }
}
GET /staff/estimates/customers Customers
Auth 60/minute

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.

Request Parameters
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.
Example Request
JSON
{
    "q": "example",
    "page": 1,
    "per_page": 1
}
Example Success Response
JSON
{
    "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
    }
}
Example Error Response
JSON
{
    "success": false,
    "code": "validation_failed",
    "message": "The given data was invalid.",
    "errors": {
        "store_id": [
            "Select an accessible active store."
        ]
    }
}
GET /staff/estimates/products Products
Auth 60/minute

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.

Request Parameters
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.
Example Request
JSON
{
    "store_id": 1,
    "warehouse_id": 1,
    "q": "example",
    "page": 1,
    "per_page": 1,
    "customer_id": 1
}
Example Success Response
JSON
{
    "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
    }
}
Example Error Response
JSON
{
    "success": false,
    "code": "validation_failed",
    "message": "The given data was invalid.",
    "errors": {
        "store_id": [
            "Select an accessible active store."
        ]
    }
}
GET /staff/estimates/products/{item} Get Staff Estimates Products
Auth 60/minute

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.

Request Parameters
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.
Example Request
JSON
{
    "store_id": 1,
    "warehouse_id": 1,
    "customer_id": 1
}
Example Success Response
JSON
{
    "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
    }
}
Example Error Response
JSON
{
    "success": false,
    "code": "validation_failed",
    "message": "The given data was invalid.",
    "errors": {
        "store_id": [
            "Select an accessible active store."
        ]
    }
}
GET /staff/estimates/products/{item}/serials Serials
Auth 60/minute

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.

Request Parameters
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.
Example Request
JSON
{
    "store_id": 1,
    "warehouse_id": 1,
    "q": "example",
    "category_id": 1,
    "customer_id": 1,
    "page": 1,
    "per_page": 1
}
Example Success Response
JSON
{
    "success": true,
    "data": {
        "serials": [
            {
                "id": 1,
                "serial_number": "EXAMPLE-SERIAL"
            }
        ]
    },
    "meta": {
        "current_page": 1,
        "last_page": 1,
        "per_page": 25,
        "total": 1
    }
}
Example Error Response
JSON
{
    "success": false,
    "code": "validation_failed",
    "message": "The given data was invalid.",
    "errors": {
        "store_id": [
            "Select an accessible active store."
        ]
    }
}
POST /staff/estimates/quote Quote
Auth 60/minute

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.

Request Parameters
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.
Example Request
JSON
{
    "store_id": 1,
    "warehouse_id": 1,
    "items": [
        {
            "item_id": 1,
            "quantity": "1.000"
        }
    ]
}
Example Success Response
JSON
{
    "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"
    }
}
Example Error Response
JSON
{
    "success": false,
    "code": "validation_failed",
    "message": "The given data was invalid.",
    "errors": {
        "store_id": [
            "Select an accessible active store."
        ]
    }
}
GET /staff/estimates/{estimate} Get Staff Estimates
Auth 60/minute

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.

Request Parameters
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.
Example Request
JSON
{
    "store_id": 1,
    "warehouse_id": 1
}
Example Success Response
JSON
{
    "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
    }
}
Example Error Response
JSON
{
    "success": false,
    "code": "validation_failed",
    "message": "The given data was invalid.",
    "errors": {
        "store_id": [
            "Select an accessible active store."
        ]
    }
}
PATCH /staff/estimates/{estimate} Update Staff Estimates
Auth 60/minute

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.

Request Parameters
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.
Example Request
JSON
{
    "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"
}
Example Success Response
JSON
{
    "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
        }
    }
}
Example Error Response
JSON
{
    "success": false,
    "code": "validation_failed",
    "message": "The given data was invalid.",
    "errors": {
        "store_id": [
            "Select an accessible active store."
        ]
    }
}
POST /staff/estimates/{estimate}/accept Accept
Auth 60/minute

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.

Request Parameters
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.
Example Request
JSON
{
    "store_id": 1,
    "warehouse_id": 1,
    "operation_key": "00000000-0000-4000-8000-000000000001",
    "revision": "opaque-example-revision"
}
Example Success Response
JSON
{
    "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
        }
    }
}
Example Error Response
JSON
{
    "success": false,
    "code": "validation_failed",
    "message": "The given data was invalid.",
    "errors": {
        "store_id": [
            "Select an accessible active store."
        ]
    }
}
POST /staff/estimates/{estimate}/convert-to-invoice Convert To Invoice
Auth 60/minute

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.

Request Parameters
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.
Example Request
JSON
{
    "store_id": 1,
    "warehouse_id": 1,
    "operation_key": "00000000-0000-4000-8000-000000000001",
    "revision": "opaque-example-revision"
}
Example Success Response
JSON
{
    "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
            }
        }
    }
}
Example Error Response
JSON
{
    "success": false,
    "code": "validation_failed",
    "message": "The given data was invalid.",
    "errors": {
        "store_id": [
            "Select an accessible active store."
        ]
    }
}
POST /staff/estimates/{estimate}/convert-to-sale Convert To Sale
Auth 60/minute

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.

Request Parameters
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.
Example Request
JSON
{
    "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"
}
Example Success Response
JSON
{
    "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
                }
            }
        }
    }
}
Example Error Response
JSON
{
    "success": false,
    "code": "validation_failed",
    "message": "The given data was invalid.",
    "errors": {
        "store_id": [
            "Select an accessible active store."
        ]
    }
}
GET /staff/estimates/{estimate}/document-deliveries Document Deliveries
Auth 60/minute

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.

Request Parameters
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.
Example Request
JSON
{
    "store_id": 1,
    "warehouse_id": 1
}
Example Success Response
JSON
{
    "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
            }
        ]
    }
}
Example Error Response
JSON
{
    "success": false,
    "code": "validation_failed",
    "message": "The given data was invalid.",
    "errors": {
        "store_id": [
            "Select an accessible active store."
        ]
    }
}
POST /staff/estimates/{estimate}/duplicate Duplicate
Auth 60/minute

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.

Request Parameters
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.
Example Request
JSON
{
    "store_id": 1,
    "warehouse_id": 1,
    "operation_key": "00000000-0000-4000-8000-000000000001",
    "revision": "opaque-example-revision"
}
Example Success Response
JSON
{
    "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
            }
        }
    }
}
Example Error Response
JSON
{
    "success": false,
    "code": "validation_failed",
    "message": "The given data was invalid.",
    "errors": {
        "store_id": [
            "Select an accessible active store."
        ]
    }
}
POST /staff/estimates/{estimate}/reject Reject
Auth 60/minute

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.

Request Parameters
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.
Example Request
JSON
{
    "store_id": 1,
    "warehouse_id": 1,
    "operation_key": "00000000-0000-4000-8000-000000000001",
    "revision": "opaque-example-revision"
}
Example Success Response
JSON
{
    "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
        }
    }
}
Example Error Response
JSON
{
    "success": false,
    "code": "validation_failed",
    "message": "The given data was invalid.",
    "errors": {
        "store_id": [
            "Select an accessible active store."
        ]
    }
}
POST /staff/estimates/{estimate}/send Send
Auth 60/minute

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.

Request Parameters
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.
Example Request
JSON
{
    "store_id": 1,
    "warehouse_id": 1,
    "operation_key": "00000000-0000-4000-8000-000000000001",
    "revision": "opaque-example-revision"
}
Example Success Response
JSON
{
    "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
        }
    }
}
Example Error Response
JSON
{
    "success": false,
    "code": "validation_failed",
    "message": "The given data was invalid.",
    "errors": {
        "store_id": [
            "Select an accessible active store."
        ]
    }
}
GET /staff/invoices List Staff Invoices
Auth 60/minute

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.

Request Parameters
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.
Example Request
JSON
{
    "store_id": 1,
    "warehouse_id": 1,
    "q": "example",
    "page": 1,
    "per_page": 1,
    "status": "example",
    "date_from": "example",
    "date_to": "example"
}
Example Success Response
JSON
{
    "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
    }
}
Example Error Response
JSON
{
    "success": false,
    "code": "validation_failed",
    "message": "The given data was invalid.",
    "errors": {
        "store_id": [
            "Select an accessible active store."
        ]
    }
}
POST /staff/invoices Create Staff Invoices
Auth 60/minute

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.

Request Parameters
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.
Example Request
JSON
{
    "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"
}
Example Success Response
JSON
{
    "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
        }
    }
}
Example Error Response
JSON
{
    "success": false,
    "code": "validation_failed",
    "message": "The given data was invalid.",
    "errors": {
        "store_id": [
            "Select an accessible active store."
        ]
    }
}
GET /staff/invoices/customers Customers
Auth 60/minute

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.

Request Parameters
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.
Example Request
JSON
{
    "q": "example",
    "page": 1,
    "per_page": 1
}
Example Success Response
JSON
{
    "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
    }
}
Example Error Response
JSON
{
    "success": false,
    "code": "validation_failed",
    "message": "The given data was invalid.",
    "errors": {
        "store_id": [
            "Select an accessible active store."
        ]
    }
}
GET /staff/invoices/editor-options Editor Options
Auth 60/minute

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.

Request Parameters
Parameter In Type Description
store_id *required query integer Selected accessible active store.
warehouse_id query integer Optional selected accessible active warehouse.
Example Request
JSON
{
    "store_id": 1,
    "warehouse_id": 1
}
Example Success Response
JSON
{
    "success": true,
    "data": {
        "withholding_tax_types": [
            {
                "id": 1,
                "name": "Example WHT",
                "rate": "3.00"
            }
        ],
        "tax_treatments": [
            "standard",
            "exempt",
            "none"
        ],
        "manual_price_allowed": true
    }
}
Example Error Response
JSON
{
    "success": false,
    "code": "validation_failed",
    "message": "The given data was invalid.",
    "errors": {
        "store_id": [
            "Select an accessible active store."
        ]
    }
}
GET /staff/invoices/products Products
Auth 60/minute

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.

Request Parameters
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.
Example Request
JSON
{
    "store_id": 1,
    "warehouse_id": 1,
    "q": "example",
    "page": 1,
    "per_page": 1,
    "customer_id": 1
}
Example Success Response
JSON
{
    "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
    }
}
Example Error Response
JSON
{
    "success": false,
    "code": "validation_failed",
    "message": "The given data was invalid.",
    "errors": {
        "store_id": [
            "Select an accessible active store."
        ]
    }
}
GET /staff/invoices/products/{item} Get Staff Invoices Products
Auth 60/minute

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.

Request Parameters
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.
Example Request
JSON
{
    "store_id": 1,
    "warehouse_id": 1,
    "customer_id": 1
}
Example Success Response
JSON
{
    "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
    }
}
Example Error Response
JSON
{
    "success": false,
    "code": "validation_failed",
    "message": "The given data was invalid.",
    "errors": {
        "store_id": [
            "Select an accessible active store."
        ]
    }
}
GET /staff/invoices/products/{item}/serials Serials
Auth 60/minute

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.

Request Parameters
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.
Example Request
JSON
{
    "store_id": 1,
    "warehouse_id": 1,
    "q": "example",
    "category_id": 1,
    "customer_id": 1,
    "page": 1,
    "per_page": 1
}
Example Success Response
JSON
{
    "success": true,
    "data": {
        "serials": [
            {
                "id": 1,
                "serial_number": "EXAMPLE-SERIAL"
            }
        ]
    },
    "meta": {
        "current_page": 1,
        "last_page": 1,
        "per_page": 25,
        "total": 1
    }
}
Example Error Response
JSON
{
    "success": false,
    "code": "validation_failed",
    "message": "The given data was invalid.",
    "errors": {
        "store_id": [
            "Select an accessible active store."
        ]
    }
}
POST /staff/invoices/quote Quote
Auth 60/minute

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.

Request Parameters
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.
Example Request
JSON
{
    "store_id": 1,
    "warehouse_id": 1,
    "items": [
        {
            "item_id": 1,
            "quantity": "1.000"
        }
    ]
}
Example Success Response
JSON
{
    "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"
    }
}
Example Error Response
JSON
{
    "success": false,
    "code": "validation_failed",
    "message": "The given data was invalid.",
    "errors": {
        "store_id": [
            "Select an accessible active store."
        ]
    }
}
GET /staff/invoices/{invoice} Get Staff Invoices
Auth 60/minute

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.

Request Parameters
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.
Example Request
JSON
{
    "store_id": 1,
    "warehouse_id": 1
}
Example Success Response
JSON
{
    "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
    }
}
Example Error Response
JSON
{
    "success": false,
    "code": "validation_failed",
    "message": "The given data was invalid.",
    "errors": {
        "store_id": [
            "Select an accessible active store."
        ]
    }
}
PATCH /staff/invoices/{invoice} Update Staff Invoices
Auth 60/minute

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.

Request Parameters
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.
Example Request
JSON
{
    "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"
}
Example Success Response
JSON
{
    "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
        }
    }
}
Example Error Response
JSON
{
    "success": false,
    "code": "validation_failed",
    "message": "The given data was invalid.",
    "errors": {
        "store_id": [
            "Select an accessible active store."
        ]
    }
}
POST /staff/invoices/{invoice}/cancel Cancel
Auth 60/minute

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.

Request Parameters
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.
Example Request
JSON
{
    "store_id": 1,
    "warehouse_id": 1,
    "operation_key": "00000000-0000-4000-8000-000000000001",
    "revision": "opaque-example-revision",
    "reason": "Customer cancelled"
}
Example Success Response
JSON
{
    "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
        }
    }
}
Example Error Response
JSON
{
    "success": false,
    "code": "validation_failed",
    "message": "The given data was invalid.",
    "errors": {
        "store_id": [
            "Select an accessible active store."
        ]
    }
}
GET /staff/invoices/{invoice}/credit-eligibility Eligibility
Auth 60/minute

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.

Request Parameters
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.
Example Request
JSON
{
    "store_id": 1,
    "warehouse_id": 1
}
Example Success Response
JSON
{
    "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
    }
}
Example Error Response
JSON
{
    "success": false,
    "code": "validation_failed",
    "message": "The given data was invalid.",
    "errors": {
        "store_id": [
            "Select an accessible active store."
        ]
    }
}
POST /staff/invoices/{invoice}/credit-notes Create Staff Credit Notes
Auth 60/minute

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.

Request Parameters
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.
Example Request
JSON
{
    "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
        }
    ]
}
Example Success Response
JSON
{
    "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
        }
    }
}
Example Error Response
JSON
{
    "success": false,
    "code": "validation_failed",
    "message": "The given data was invalid.",
    "errors": {
        "store_id": [
            "Select an accessible active store."
        ]
    }
}
GET /staff/invoices/{invoice}/document-deliveries Document Deliveries
Auth 60/minute

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.

Request Parameters
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.
Example Request
JSON
{
    "store_id": 1,
    "warehouse_id": 1
}
Example Success Response
JSON
{
    "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
            }
        ]
    }
}
Example Error Response
JSON
{
    "success": false,
    "code": "validation_failed",
    "message": "The given data was invalid.",
    "errors": {
        "store_id": [
            "Select an accessible active store."
        ]
    }
}
POST /staff/invoices/{invoice}/finalize Finalize
Auth 60/minute

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.

Request Parameters
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.
Example Request
JSON
{
    "store_id": 1,
    "warehouse_id": 1,
    "operation_key": "00000000-0000-4000-8000-000000000001",
    "revision": "opaque-example-revision"
}
Example Success Response
JSON
{
    "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
        }
    }
}
Example Error Response
JSON
{
    "success": false,
    "code": "validation_failed",
    "message": "The given data was invalid.",
    "errors": {
        "store_id": [
            "Select an accessible active store."
        ]
    }
}
POST /staff/invoices/{invoice}/payments Create Staff Invoices Payments
Auth 60/minute

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.

Request Parameters
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.
Example Request
JSON
{
    "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
}
Example Success Response
JSON
{
    "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"
            }
        }
    }
}
Example Error Response
JSON
{
    "success": false,
    "code": "validation_failed",
    "message": "The given data was invalid.",
    "errors": {
        "store_id": [
            "Select an accessible active store."
        ]
    }
}
GET /staff/invoices/{invoice}/payments/{payment}/document-deliveries Document Deliveries
Auth 60/minute

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.

Request Parameters
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.
Example Request
JSON
{
    "store_id": 1,
    "warehouse_id": 1
}
Example Success Response
JSON
{
    "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
            }
        ]
    }
}
Example Error Response
JSON
{
    "success": false,
    "code": "validation_failed",
    "message": "The given data was invalid.",
    "errors": {
        "store_id": [
            "Select an accessible active store."
        ]
    }
}
DELETE /staff/invoices/{invoice}/share Revoke
Auth 60/minute

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.

Request Parameters
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.
Example Request
JSON
{
    "store_id": 1,
    "warehouse_id": 1
}
Example Success Response
JSON
{
    "success": true,
    "data": {
        "revoked": true
    }
}
Example Error Response
JSON
{
    "success": false,
    "code": "validation_failed",
    "message": "The given data was invalid.",
    "errors": {
        "store_id": [
            "Select an accessible active store."
        ]
    }
}
POST /staff/invoices/{invoice}/share Share
Auth 60/minute

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.

Request Parameters
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.
Example Request
JSON
{
    "store_id": 1,
    "warehouse_id": 1,
    "regenerate": false
}
Example Success Response
JSON
{
    "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"
    }
}
Example Error Response
JSON
{
    "success": false,
    "code": "validation_failed",
    "message": "The given data was invalid.",
    "errors": {
        "store_id": [
            "Select an accessible active store."
        ]
    }
}
GET /staff/operations/{key} Get Staff Operations
Auth 60/minute

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.

Request Parameters
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.
Example Request
JSON
{
    "store_id": 1,
    "warehouse_id": 1
}
Example Success Response
JSON
{
    "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
            }
        }
    }
}
Example Error Response
JSON
{
    "success": false,
    "code": "validation_failed",
    "message": "The given data was invalid.",
    "errors": {
        "store_id": [
            "Select an accessible active store."
        ]
    }
}
POST /staff/pos/checkout Checkout
Auth 60/minute

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.

Request Parameters
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.
Example Request
JSON
{
    "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"
        }
    ]
}
Example Success Response
JSON
{
    "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
            }
        }
    }
}
Example Error Response
JSON
{
    "success": false,
    "code": "validation_failed",
    "message": "The given data was invalid.",
    "errors": {
        "store_id": [
            "Select an accessible active store."
        ]
    }
}
GET /staff/pos/customers Customers
Auth 60/minute

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.

Request Parameters
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.
Example Request
JSON
{
    "q": "example",
    "page": 1,
    "per_page": 1
}
Example Success Response
JSON
{
    "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
    }
}
Example Error Response
JSON
{
    "success": false,
    "code": "validation_failed",
    "message": "The given data was invalid.",
    "errors": {
        "store_id": [
            "Select an accessible active store."
        ]
    }
}
POST /staff/pos/discount-approval Discount Approval
Auth 60/minute

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.

Request Parameters
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.
email 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.
Example Request
JSON
{
    "store_id": 1,
    "warehouse_id": 1,
    "items": [
        {
            "id": 1,
            "qty": "1.000"
        }
    ],
    "email": "manager@example.invalid",
    "pin": "0000"
}
Example Success Response
JSON
{
    "success": true,
    "data": {
        "approval_token": "SYNTHETIC-ENCRYPTED-APPROVAL",
        "expires_at": "2026-10-05T12: 05: 00+00: 00"
    }
}
Example Error Response
JSON
{
    "success": false,
    "code": "validation_failed",
    "message": "The given data was invalid.",
    "errors": {
        "store_id": [
            "Select an accessible active store."
        ]
    }
}
GET /staff/pos/discount-approvers Discount Approvers
Auth 60/minute

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.

Request Parameters
Parameter In Type Description
store_id *required query integer Selected accessible active store.
warehouse_id query integer Optional selected accessible active warehouse.
Example Request
JSON
{
    "store_id": 1,
    "warehouse_id": 1
}
Example Success Response
JSON
{
    "success": true,
    "data": {
        "approvers": [
            {
                "id": 2,
                "name": "Example Manager",
                "role": "Store Manager",
                "signed_in_today": true
            }
        ]
    }
}
Example Error Response
JSON
{
    "success": false,
    "code": "validation_failed",
    "message": "The given data was invalid.",
    "errors": {
        "store_id": [
            "Select an accessible active store."
        ]
    }
}
GET /staff/pos/discount-limits Discount Limits
Auth 60/minute

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.

Request Parameters
Parameter In Type Description
store_id *required query integer Selected accessible active store.
warehouse_id query integer Optional selected accessible active warehouse.
Example Request
JSON
{
    "store_id": 1,
    "warehouse_id": 1
}
Example Success Response
JSON
{
    "success": true,
    "data": {
        "max_discount_percent": "5.00",
        "max_discount_amount": {
            "amount": "10.00",
            "currency": "GHS",
            "formatted": "GH\u20b510.00"
        }
    }
}
Example Error Response
JSON
{
    "success": false,
    "code": "validation_failed",
    "message": "The given data was invalid.",
    "errors": {
        "store_id": [
            "Select an accessible active store."
        ]
    }
}
GET /staff/pos/drafts List Staff Pos Drafts
Auth 60/minute

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.

Request Parameters
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.
Example Request
JSON
{
    "store_id": 1,
    "warehouse_id": 1,
    "page": 1,
    "per_page": 1
}
Example Success Response
JSON
{
    "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
    }
}
Example Error Response
JSON
{
    "success": false,
    "code": "validation_failed",
    "message": "The given data was invalid.",
    "errors": {
        "store_id": [
            "Select an accessible active store."
        ]
    }
}
POST /staff/pos/drafts Create Staff Pos Drafts
Auth 60/minute

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.

Request Parameters
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.
Example Request
JSON
{
    "store_id": 1,
    "warehouse_id": 1,
    "operation_key": "00000000-0000-4000-8000-000000000001",
    "contents": {
        "items": [
            {
                "id": 1,
                "qty": "1.000"
            }
        ]
    }
}
Example Success Response
JSON
{
    "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
        }
    }
}
Example Error Response
JSON
{
    "success": false,
    "code": "validation_failed",
    "message": "The given data was invalid.",
    "errors": {
        "store_id": [
            "Select an accessible active store."
        ]
    }
}
DELETE /staff/pos/drafts/{draft} Delete Staff Pos Drafts
Auth 60/minute

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.

Request Parameters
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.
Example Request
JSON
{
    "store_id": 1,
    "warehouse_id": 1,
    "revision": "opaque-example-revision"
}
Example Success Response
JSON
{
    "success": true,
    "data": {
        "deleted": true
    }
}
Example Error Response
JSON
{
    "success": false,
    "code": "validation_failed",
    "message": "The given data was invalid.",
    "errors": {
        "store_id": [
            "Select an accessible active store."
        ]
    }
}
GET /staff/pos/drafts/{draft} Get Staff Pos Drafts
Auth 60/minute

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.

Request Parameters
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.
Example Request
JSON
{
    "store_id": 1,
    "warehouse_id": 1
}
Example Success Response
JSON
{
    "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": []
    }
}
Example Error Response
JSON
{
    "success": false,
    "code": "validation_failed",
    "message": "The given data was invalid.",
    "errors": {
        "store_id": [
            "Select an accessible active store."
        ]
    }
}
PATCH /staff/pos/drafts/{draft} Update Staff Pos Drafts
Auth 60/minute

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.

Request Parameters
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.
Example Request
JSON
{
    "store_id": 1,
    "warehouse_id": 1,
    "operation_key": "00000000-0000-4000-8000-000000000001",
    "contents": {
        "items": [
            {
                "id": 1,
                "qty": "1.000"
            }
        ]
    },
    "revision": "opaque-example-revision"
}
Example Success Response
JSON
{
    "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
        }
    }
}
Example Error Response
JSON
{
    "success": false,
    "code": "validation_failed",
    "message": "The given data was invalid.",
    "errors": {
        "store_id": [
            "Select an accessible active store."
        ]
    }
}
GET /staff/pos/products List Staff Pos Products
Auth 60/minute

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.

Request Parameters
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.
Example Request
JSON
{
    "store_id": 1,
    "warehouse_id": 1,
    "q": "example",
    "category_id": 1,
    "customer_id": 1,
    "page": 1,
    "per_page": 1
}
Example Success Response
JSON
{
    "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
    }
}
Example Error Response
JSON
{
    "success": false,
    "code": "validation_failed",
    "message": "The given data was invalid.",
    "errors": {
        "store_id": [
            "Select an accessible active store."
        ]
    }
}
GET /staff/pos/products/{item} Get Staff Pos Products
Auth 60/minute

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.

Request Parameters
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.
Example Request
JSON
{
    "store_id": 1,
    "warehouse_id": 1,
    "q": "example",
    "category_id": 1,
    "customer_id": 1,
    "page": 1,
    "per_page": 1
}
Example Success Response
JSON
{
    "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
        }
    }
}
Example Error Response
JSON
{
    "success": false,
    "code": "validation_failed",
    "message": "The given data was invalid.",
    "errors": {
        "store_id": [
            "Select an accessible active store."
        ]
    }
}
GET /staff/pos/products/{item}/serials Serials
Auth 60/minute

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.

Request Parameters
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.
Example Request
JSON
{
    "store_id": 1,
    "warehouse_id": 1,
    "q": "example",
    "category_id": 1,
    "customer_id": 1,
    "page": 1,
    "per_page": 1
}
Example Success Response
JSON
{
    "success": true,
    "data": {
        "serials": [
            {
                "id": 1,
                "serial_number": "EXAMPLE-SERIAL"
            }
        ]
    },
    "meta": {
        "current_page": 1,
        "last_page": 1,
        "per_page": 25,
        "total": 1
    }
}
Example Error Response
JSON
{
    "success": false,
    "code": "validation_failed",
    "message": "The given data was invalid.",
    "errors": {
        "store_id": [
            "Select an accessible active store."
        ]
    }
}
POST /staff/pos/quote Quote
Auth 60/minute

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.

Request Parameters
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.
Example Request
JSON
{
    "store_id": 1,
    "warehouse_id": 1,
    "items": [
        {
            "id": 1,
            "qty": "1.000"
        }
    ]
}
Example Success Response
JSON
{
    "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"
    }
}
Example Error Response
JSON
{
    "success": false,
    "code": "validation_failed",
    "message": "The given data was invalid.",
    "errors": {
        "store_id": [
            "Select an accessible active store."
        ]
    }
}
GET /staff/reports List Staff Reports
Auth 60/minute

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.

Example Request
JSON
[]
Example Success Response
JSON
{
    "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"
                ]
            }
        ]
    }
}
Example Error Response
JSON
{
    "success": false,
    "code": "validation_failed",
    "message": "The given data was invalid.",
    "errors": {
        "store_id": [
            "Select an accessible active store."
        ]
    }
}
GET /staff/reports/{reportKey} Get Staff Reports
Auth 60/minute

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}}]}}

Request Parameters
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.
Example Request
JSON
{
    "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"
}
Example Success Response
JSON
{
    "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"
        ]
    }
}
Example Error Response
JSON
{
    "success": false,
    "code": "validation_failed",
    "message": "The given data was invalid.",
    "errors": {
        "store_id": [
            "Select an accessible active store."
        ]
    }
}
GET /staff/returns List Staff Returns
Auth 60/minute

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.

Request Parameters
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.
Example Request
JSON
{
    "store_id": 1,
    "warehouse_id": 1,
    "q": "example",
    "status": "example",
    "date_from": "example",
    "date_to": "example",
    "page": 1,
    "per_page": 1,
    "sale_id": 1
}
Example Success Response
JSON
{
    "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
    }
}
Example Error Response
JSON
{
    "success": false,
    "code": "validation_failed",
    "message": "The given data was invalid.",
    "errors": {
        "store_id": [
            "Select an accessible active store."
        ]
    }
}
GET /staff/returns/{return} Get Staff Returns
Auth 60/minute

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.

Request Parameters
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.
Example Request
JSON
{
    "store_id": 1,
    "warehouse_id": 1
}
Example Success Response
JSON
{
    "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
        }
    }
}
Example Error Response
JSON
{
    "success": false,
    "code": "validation_failed",
    "message": "The given data was invalid.",
    "errors": {
        "store_id": [
            "Select an accessible active store."
        ]
    }
}
POST /staff/returns/{return}/approve Approve
Auth 60/minute

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.

Request Parameters
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.
Example Request
JSON
{
    "store_id": 1,
    "warehouse_id": 1,
    "operation_key": "00000000-0000-4000-8000-000000000001",
    "revision": "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"
}
Example Success Response
JSON
{
    "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
            }
        }
    }
}
Example Error Response
JSON
{
    "success": false,
    "code": "validation_failed",
    "message": "The given data was invalid.",
    "errors": {
        "store_id": [
            "Select an accessible active store."
        ]
    }
}
POST /staff/returns/{return}/cancel Cancel
Auth 60/minute

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.

Request Parameters
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.
Example Request
JSON
{
    "store_id": 1,
    "warehouse_id": 1,
    "operation_key": "00000000-0000-4000-8000-000000000001",
    "revision": "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"
}
Example Success Response
JSON
{
    "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
            }
        }
    }
}
Example Error Response
JSON
{
    "success": false,
    "code": "validation_failed",
    "message": "The given data was invalid.",
    "errors": {
        "store_id": [
            "Select an accessible active store."
        ]
    }
}
POST /staff/returns/{return}/process Process
Auth 60/minute

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.

Request Parameters
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.
Example Request
JSON
{
    "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"
        }
    ]
}
Example Success Response
JSON
{
    "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
            }
        }
    }
}
Example Error Response
JSON
{
    "success": false,
    "code": "validation_failed",
    "message": "The given data was invalid.",
    "errors": {
        "store_id": [
            "Select an accessible active store."
        ]
    }
}
POST /staff/returns/{return}/reject Reject
Auth 60/minute

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.

Request Parameters
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.
Example Request
JSON
{
    "store_id": 1,
    "warehouse_id": 1,
    "operation_key": "00000000-0000-4000-8000-000000000001",
    "revision": "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa",
    "reason": "Return not eligible"
}
Example Success Response
JSON
{
    "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
            }
        }
    }
}
Example Error Response
JSON
{
    "success": false,
    "code": "validation_failed",
    "message": "The given data was invalid.",
    "errors": {
        "store_id": [
            "Select an accessible active store."
        ]
    }
}
GET /staff/sales List Staff Sales
Auth 60/minute

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.

Request Parameters
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.
Example Request
JSON
{
    "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
}
Example Success Response
JSON
{
    "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
    }
}
Example Error Response
JSON
{
    "success": false,
    "code": "validation_failed",
    "message": "The given data was invalid.",
    "errors": {
        "store_id": [
            "Select an accessible active store."
        ]
    }
}
GET /staff/sales/{sale} Get Staff Sales
Auth 60/minute

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.

Request Parameters
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.
Example Request
JSON
{
    "store_id": 1,
    "warehouse_id": 1
}
Example Success Response
JSON
{
    "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
    }
}
Example Error Response
JSON
{
    "success": false,
    "code": "validation_failed",
    "message": "The given data was invalid.",
    "errors": {
        "store_id": [
            "Select an accessible active store."
        ]
    }
}
GET /staff/sales/{sale}/receipt.pdf Receipt
Auth 60/minute

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.

Request Parameters
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.
Example Request
JSON
{
    "store_id": 1,
    "warehouse_id": 1
}
Example Success Response
JSON
"%PDF-1.7 (synthetic binary example)"
Example Error Response
JSON
{
    "success": false,
    "code": "validation_failed",
    "message": "The given data was invalid.",
    "errors": {
        "store_id": [
            "Select an accessible active store."
        ]
    }
}
GET /staff/sales/{sale}/return-eligibility Eligibility
Auth 60/minute

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.

Request Parameters
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.
Example Request
JSON
{
    "store_id": 1,
    "warehouse_id": 1
}
Example Success Response
JSON
{
    "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
    }
}
Example Error Response
JSON
{
    "success": false,
    "code": "validation_failed",
    "message": "The given data was invalid.",
    "errors": {
        "store_id": [
            "Select an accessible active store."
        ]
    }
}
POST /staff/sales/{sale}/returns Create Staff Returns
Auth 60/minute

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.

Request Parameters
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.
Example Request
JSON
{
    "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
        }
    ]
}
Example Success Response
JSON
{
    "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
            }
        }
    }
}
Example Error Response
JSON
{
    "success": false,
    "code": "validation_failed",
    "message": "The given data was invalid.",
    "errors": {
        "store_id": [
            "Select an accessible active store."
        ]
    }
}
GET /staff/web-orders List Staff Web Orders
Auth 60/minute

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.

Request Parameters
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.
Example Request
JSON
{
    "store_id": 1,
    "warehouse_id": 1,
    "queue": "example",
    "q": "example",
    "page": 1
}
Example Success Response
JSON
{
    "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
    }
}
Example Error Response
JSON
{
    "success": false,
    "code": "validation_failed",
    "message": "The given data was invalid.",
    "errors": {
        "store_id": [
            "Select an accessible active store."
        ]
    }
}
GET /staff/web-orders/{order} Get Staff Web Orders
Auth 60/minute

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.

Request Parameters
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.
Example Request
JSON
{
    "store_id": 1,
    "warehouse_id": 1
}
Example Success Response
JSON
{
    "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
    }
}
Example Error Response
JSON
{
    "success": false,
    "code": "validation_failed",
    "message": "The given data was invalid.",
    "errors": {
        "store_id": [
            "Select an accessible active store."
        ]
    }
}
POST /staff/web-orders/{order}/complete-sale Complete Sale
Auth 60/minute

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.

Request Parameters
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.
Example Request
JSON
{
    "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
        ]
    }
}
Example Success Response
JSON
{
    "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."
}
Example Error Response
JSON
{
    "success": false,
    "code": "validation_failed",
    "message": "The given data was invalid.",
    "errors": {
        "store_id": [
            "Select an accessible active store."
        ]
    }
}
POST /staff/web-orders/{order}/follow-up Follow Up
Auth 60/minute

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.

Request Parameters
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.
Example Request
JSON
{
    "store_id": 1,
    "operation_key": "00000000-0000-4000-8000-000000000001",
    "revision": "opaque-example-revision",
    "status": "contacted",
    "notes": "Example customer follow-up"
}
Example Success Response
JSON
{
    "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."
}
Example Error Response
JSON
{
    "success": false,
    "code": "validation_failed",
    "message": "The given data was invalid.",
    "errors": {
        "store_id": [
            "Select an accessible active store."
        ]
    }
}
POST /staff/web-orders/{order}/fulfillment Transition
Auth 60/minute

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.

Request Parameters
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.
Example Request
JSON
{
    "store_id": 1,
    "operation_key": "00000000-0000-4000-8000-000000000001",
    "revision": "opaque-example-revision",
    "status": "preparing"
}
Example Success Response
JSON
{
    "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."
}
Example Error Response
JSON
{
    "success": false,
    "code": "validation_failed",
    "message": "The given data was invalid.",
    "errors": {
        "store_id": [
            "Select an accessible active store."
        ]
    }
}
GET /staff/web-orders/{order}/items/{orderItem}/serials Serials
Auth 60/minute

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.

Request Parameters
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.
Example Request
JSON
{
    "store_id": 1,
    "warehouse_id": 1,
    "q": "example",
    "page": 1
}
Example Success Response
JSON
{
    "success": true,
    "data": {
        "serials": [
            {
                "id": 1,
                "serial_number": "EXAMPLE-SERIAL-001"
            }
        ]
    },
    "meta": {
        "current_page": 1,
        "last_page": 1,
        "per_page": 50,
        "total": 1
    }
}
Example Error Response
JSON
{
    "success": false,
    "code": "validation_failed",
    "message": "The given data was invalid.",
    "errors": {
        "store_id": [
            "Select an accessible active store."
        ]
    }
}
POST /staff/web-orders/{order}/notes Notes
Auth 60/minute

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.

Request Parameters
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.
Example Request
JSON
{
    "store_id": 1,
    "operation_key": "00000000-0000-4000-8000-000000000001",
    "revision": "opaque-example-revision",
    "body": "Example internal note"
}
Example Success Response
JSON
{
    "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."
}
Example Error Response
JSON
{
    "success": false,
    "code": "validation_failed",
    "message": "The given data was invalid.",
    "errors": {
        "store_id": [
            "Select an accessible active store."
        ]
    }
}
GET /staff/web-orders/{order}/settlement Settlement
Auth 60/minute

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.

Request Parameters
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.
Example Request
JSON
{
    "store_id": 1,
    "warehouse_id": 1
}
Example Success Response
JSON
{
    "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
            }
        ]
    }
}
Example Error Response
JSON
{
    "success": false,
    "code": "validation_failed",
    "message": "The given data was invalid.",
    "errors": {
        "store_id": [
            "Select an accessible active store."
        ]
    }
}
POST /staff/web-orders/{order}/settlement/quote Quote
Auth 60/minute

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.

Request Parameters
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.
Example Request
JSON
{
    "store_id": 1,
    "delivery_fee": "0.00"
}
Example Success Response
JSON
{
    "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"
    }
}
Example Error Response
JSON
{
    "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.

POST /auth/login User Login
5/minute

Authenticate a user and issue a Sanctum token for API requests.

Request Parameters
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
Example Request
JSON
{
    "email": "cashier@example.com",
    "password": "secret123",
    "device_name": "iPhone 15 Pro",
    "store_id": 1
}
Example Success Response
JSON
{
    "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
    }
}
Example Error Response
JSON
{
    "success": false,
    "message": "The given data was invalid.",
    "errors": {
        "email": [
            "The provided credentials are incorrect."
        ]
    }
}
POST /auth/logout User Logout
Auth 60/minute

Revoke the current token and end the current API session.

Example Request
JSON
[]
Example Success Response
JSON
{
    "success": true,
    "data": {
        "message": "Successfully logged out."
    }
}
Example Error Response
JSON
{
    "success": false,
    "message": "Unauthorized."
}
GET /auth/me Current User Profile
Auth 60/minute

Return the authenticated user profile, permissions, and discount limits.

Example Request
JSON
[]
Example Success Response
JSON
{
    "success": true,
    "data": {
        "id": 1,
        "name": "Example"
    }
}
Example Error Response
JSON
{
    "success": false,
    "message": "Unauthorized."
}

Products & Catalog

Items, categories, stores, warehouses, and reference data.

GET /categories List Categories
Auth 60/minute

Get paginated categories with optional hierarchy and count metadata.

Example Request
JSON
[]
Example Success Response
JSON
{
    "success": true,
    "data": [
        {
            "id": 1,
            "name": "Example"
        }
    ]
}
Example Error Response
JSON
{
    "success": false,
    "message": "Unauthorized."
}
POST /categories Create Categories
Auth 60/minute

Submit a Categories request to /categories.

Example Request
JSON
[]
Example Success Response
JSON
{
    "success": true,
    "data": [
        {
            "id": 1,
            "name": "Example"
        }
    ]
}
Example Error Response
JSON
{
    "success": false,
    "message": "Unauthorized."
}
DELETE /categories/{category} Delete Categories
Auth 60/minute

Delete Categories data through /categories/{category}.

Request Parameters
Parameter In Type Description
category *required path string Path parameter: Category
Example Request
JSON
[]
Example Success Response
JSON
{
    "success": true,
    "message": "Operation completed successfully."
}
Example Error Response
JSON
{
    "success": false,
    "message": "Unauthorized."
}
GET /categories/{category} Get Categories
Auth 60/minute

Retrieve Categories data from /categories/{category}.

Request Parameters
Parameter In Type Description
category *required path string Path parameter: Category
Example Request
JSON
[]
Example Success Response
JSON
{
    "success": true,
    "data": {
        "id": 1,
        "name": "Example"
    }
}
Example Error Response
JSON
{
    "success": false,
    "message": "Unauthorized."
}
PUT /categories/{category} Update Categories
Auth 60/minute

Update Categories data through /categories/{category}.

Request Parameters
Parameter In Type Description
category *required path string Path parameter: Category
Example Request
JSON
[]
Example Success Response
JSON
{
    "success": true,
    "data": {
        "id": 1,
        "name": "Example"
    }
}
Example Error Response
JSON
{
    "success": false,
    "message": "Unauthorized."
}
GET /items List Items
Auth 60/minute

Get paginated items with optional search by query, SKU, barcode, or related catalog metadata.

Request Parameters
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)
Example Request
JSON
{
    "q": "example",
    "search": "example",
    "per_page": 1
}
Example Success Response
JSON
{
    "success": true,
    "data": [
        {
            "id": 1,
            "name": "Example"
        }
    ],
    "meta": {
        "current_page": 1,
        "last_page": 1,
        "per_page": 50,
        "total": 1
    }
}
Example Error Response
JSON
{
    "success": false,
    "message": "Unauthorized."
}
GET /items/check-name Check Name
Auth 60/minute

Retrieve Items data from /items/check-name.

Example Request
JSON
[]
Example Success Response
JSON
{
    "success": true,
    "data": {
        "id": 1,
        "name": "Example"
    }
}
Example Error Response
JSON
{
    "success": false,
    "message": "Unauthorized."
}
GET /items/{item}/applicable-price-books Applicable Price Books
Auth 60/minute

Retrieve Items data from /items/{item}/applicable-price-books.

Request Parameters
Parameter In Type Description
item *required path string Path parameter: Item
Example Request
JSON
[]
Example Success Response
JSON
{
    "success": true,
    "data": {
        "id": 1,
        "name": "Example"
    }
}
Example Error Response
JSON
{
    "success": false,
    "message": "Unauthorized."
}
GET /items/{item}/price Price
Auth 60/minute

Retrieve Items data from /items/{item}/price.

Request Parameters
Parameter In Type Description
item *required path string Path parameter: Item
Example Request
JSON
[]
Example Success Response
JSON
{
    "success": true,
    "data": {
        "id": 1,
        "name": "Example"
    }
}
Example Error Response
JSON
{
    "success": false,
    "message": "Unauthorized."
}
GET /items/{item}/price-breakdown Price Breakdown
Auth 60/minute

Retrieve Items data from /items/{item}/price-breakdown.

Request Parameters
Parameter In Type Description
item *required path string Path parameter: Item
Example Request
JSON
[]
Example Success Response
JSON
{
    "success": true,
    "data": {
        "id": 1,
        "name": "Example"
    }
}
Example Error Response
JSON
{
    "success": false,
    "message": "Unauthorized."
}
GET /payment-methods List Payment Methods
Auth 60/minute

Retrieve Payment Methods data from /payment-methods.

Example Request
JSON
[]
Example Success Response
JSON
{
    "success": true,
    "data": [
        {
            "id": 1,
            "name": "Example"
        }
    ]
}
Example Error Response
JSON
{
    "success": false,
    "message": "Unauthorized."
}
GET /payment-methods/{paymentMethod} Get Payment Methods
Auth 60/minute

Retrieve Payment Methods data from /payment-methods/{paymentMethod}.

Request Parameters
Parameter In Type Description
paymentMethod *required path string Path parameter: Payment Method
Example Request
JSON
[]
Example Success Response
JSON
{
    "success": true,
    "data": {
        "id": 1,
        "name": "Example"
    }
}
Example Error Response
JSON
{
    "success": false,
    "message": "Unauthorized."
}
GET /price-books List Price Books
Auth 60/minute

Retrieve Price Books data from /price-books.

Example Request
JSON
[]
Example Success Response
JSON
{
    "success": true,
    "data": [
        {
            "id": 1,
            "name": "Example"
        }
    ]
}
Example Error Response
JSON
{
    "success": false,
    "message": "Unauthorized."
}
GET /price-books/{priceBook} Get Price Books
Auth 60/minute

Retrieve Price Books data from /price-books/{priceBook}.

Request Parameters
Parameter In Type Description
priceBook *required path string Path parameter: Price Book
Example Request
JSON
[]
Example Success Response
JSON
{
    "success": true,
    "data": {
        "id": 1,
        "name": "Example"
    }
}
Example Error Response
JSON
{
    "success": false,
    "message": "Unauthorized."
}
GET /stores List Accessible Stores
Auth 60/minute

Return stores the authenticated user can access, with optional warehouses and payment methods.

Example Request
JSON
[]
Example Success Response
JSON
{
    "success": true,
    "data": [
        {
            "id": 1,
            "name": "Example"
        }
    ]
}
Example Error Response
JSON
{
    "success": false,
    "message": "Unauthorized."
}
GET /stores/{store} Get Stores
Auth 60/minute

Retrieve Stores data from /stores/{store}.

Request Parameters
Parameter In Type Description
store *required path string Path parameter: Store
Example Request
JSON
[]
Example Success Response
JSON
{
    "success": true,
    "data": {
        "id": 1,
        "name": "Example"
    }
}
Example Error Response
JSON
{
    "success": false,
    "message": "Unauthorized."
}
GET /stores/{store}/warehouses List Store Warehouses
Auth 60/minute

Return active warehouses for a specific store, optionally including standalone warehouses that serve it.

Request Parameters
Parameter In Type Description
store *required path string Path parameter: Store
Example Request
JSON
[]
Example Success Response
JSON
{
    "success": true,
    "data": {
        "id": 1,
        "name": "Example"
    }
}
Example Error Response
JSON
{
    "success": false,
    "message": "Unauthorized."
}
GET /warehouses List Warehouses
Auth 60/minute

Return active warehouses with store and standalone filtering options.

Example Request
JSON
[]
Example Success Response
JSON
{
    "success": true,
    "data": [
        {
            "id": 1,
            "name": "Example"
        }
    ]
}
Example Error Response
JSON
{
    "success": false,
    "message": "Unauthorized."
}
GET /warehouses/standalone Standalone
Auth 60/minute

Retrieve Warehouses data from /warehouses/standalone.

Example Request
JSON
[]
Example Success Response
JSON
{
    "success": true,
    "data": {
        "id": 1,
        "name": "Example"
    }
}
Example Error Response
JSON
{
    "success": false,
    "message": "Unauthorized."
}
GET /warehouses/{warehouse} Get Warehouses
Auth 60/minute

Retrieve Warehouses data from /warehouses/{warehouse}.

Request Parameters
Parameter In Type Description
warehouse *required path string Path parameter: Warehouse
Example Request
JSON
[]
Example Success Response
JSON
{
    "success": true,
    "data": {
        "id": 1,
        "name": "Example"
    }
}
Example Error Response
JSON
{
    "success": false,
    "message": "Unauthorized."
}

POS Operations

POS catalog, checkout, tax, discounts, and session status.

POST /pos/checkout Create POS Checkout
Auth 60/minute

Validate, price, tax, and complete a POS sale.

Request Parameters
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
Example Request
JSON
{
    "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"
}
Example Success Response
JSON
{
    "success": true,
    "data": {
        "id": 1,
        "name": "Example"
    }
}
Example Error Response
JSON
{
    "success": false,
    "message": "Unauthorized."
}
POST /pos/checkout/validate Validate
Auth 60/minute

Submit a Pos Checkout request to /pos/checkout/validate.

Example Request
JSON
[]
Example Success Response
JSON
{
    "success": true,
    "data": {
        "id": 1,
        "name": "Example"
    }
}
Example Error Response
JSON
{
    "success": false,
    "message": "Unauthorized."
}
GET /pos/discount-limits Discount Limits
Auth 60/minute

Retrieve Pos data from /pos/discount-limits.

Example Request
JSON
[]
Example Success Response
JSON
{
    "success": true,
    "data": [
        {
            "id": 1,
            "name": "Example"
        }
    ]
}
Example Error Response
JSON
{
    "success": false,
    "message": "Unauthorized."
}
POST /pos/discount/approve Approve
Auth 60/minute

Submit a Pos Discount request to /pos/discount/approve.

Example Request
JSON
[]
Example Success Response
JSON
{
    "success": true,
    "data": {
        "id": 1,
        "name": "Example"
    }
}
Example Error Response
JSON
{
    "success": false,
    "message": "Unauthorized."
}
GET /pos/products List POS Products
Auth 60/minute

Get paginated POS-ready products with live price and stock data.

Example Request
JSON
[]
Example Success Response
JSON
{
    "success": true,
    "data": [
        {
            "id": 1,
            "name": "Example"
        }
    ]
}
Example Error Response
JSON
{
    "success": false,
    "message": "Unauthorized."
}
GET /pos/products/search Search POS Products
Auth 60/minute

Search POS products by name, SKU, or barcode.

Request Parameters
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
Example Request
JSON
{
    "q": "example",
    "store_id": 1,
    "warehouse_id": 1,
    "category_id": 1,
    "limit": 1
}
Example Success Response
JSON
{
    "success": true,
    "data": [
        {
            "id": 1,
            "name": "Example"
        }
    ]
}
Example Error Response
JSON
{
    "success": false,
    "message": "Unauthorized."
}
GET /pos/products/{item} Get Pos Products
Auth 60/minute

Retrieve Pos Products data from /pos/products/{item}.

Request Parameters
Parameter In Type Description
item *required path string Path parameter: Item
Example Request
JSON
[]
Example Success Response
JSON
{
    "success": true,
    "data": {
        "id": 1,
        "name": "Example"
    }
}
Example Error Response
JSON
{
    "success": false,
    "message": "Unauthorized."
}
GET /pos/session-status POS Session Status
Auth 60/minute

Return POS readiness, including store access and active cashier session status.

Example Request
JSON
[]
Example Success Response
JSON
{
    "success": true,
    "data": [
        {
            "id": 1,
            "name": "Example"
        }
    ]
}
Example Error Response
JSON
{
    "success": false,
    "message": "Unauthorized."
}
POST /pos/tax/calculate Calculate POS Tax
Auth 60/minute

Return tax details for a POS cart before checkout.

Example Request
JSON
[]
Example Success Response
JSON
{
    "success": true,
    "data": {
        "id": 1,
        "name": "Example"
    }
}
Example Error Response
JSON
{
    "success": false,
    "message": "Unauthorized."
}
GET /pos/tax/info Info
Auth 60/minute

Retrieve Pos Tax data from /pos/tax/info.

Example Request
JSON
[]
Example Success Response
JSON
{
    "success": true,
    "data": {
        "id": 1,
        "name": "Example"
    }
}
Example Error Response
JSON
{
    "success": false,
    "message": "Unauthorized."
}

Cashier Sessions

Cashier shift open, close, suspend, and cash movement APIs.

GET /sessions/current Current Cashier Session
Auth 60/minute

Return the open cashier session, if any, for the current user and store.

Example Request
JSON
[]
Example Success Response
JSON
{
    "success": true,
    "data": {
        "id": 1,
        "name": "Example"
    }
}
Example Error Response
JSON
{
    "success": false,
    "message": "Unauthorized."
}
POST /sessions/open Open Cashier Session
Auth 60/minute

Open a new cashier session for the authenticated user.

Request Parameters
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
Example Request
JSON
{
    "store_id": 1,
    "opening_balance": 9.99,
    "cash_register_id": 1,
    "notes": "example"
}
Example Success Response
JSON
{
    "success": true,
    "data": {
        "id": 1,
        "name": "Example"
    }
}
Example Error Response
JSON
{
    "success": false,
    "message": "Unauthorized."
}
GET /sessions/{session} Get Sessions
Auth 60/minute

Retrieve Sessions data from /sessions/{session}.

Request Parameters
Parameter In Type Description
session *required path string Path parameter: Session
Example Request
JSON
[]
Example Success Response
JSON
{
    "success": true,
    "data": {
        "id": 1,
        "name": "Example"
    }
}
Example Error Response
JSON
{
    "success": false,
    "message": "Unauthorized."
}
POST /sessions/{session}/cash-movement Cash Movement
Auth 60/minute

Submit a Sessions request to /sessions/{session}/cash-movement.

Request Parameters
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
Example Request
JSON
{
    "type": "example",
    "amount": 9.99,
    "reason": "example",
    "notes": "example",
    "reference": "example"
}
Example Success Response
JSON
{
    "success": true,
    "data": {
        "id": 1,
        "name": "Example"
    }
}
Example Error Response
JSON
{
    "success": false,
    "message": "Unauthorized."
}
POST /sessions/{session}/close Close
Auth 60/minute

Submit a Sessions request to /sessions/{session}/close.

Request Parameters
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
Example Request
JSON
{
    "closing_balance": 9.99,
    "notes": "example",
    "discrepancy_notes": "example"
}
Example Success Response
JSON
{
    "success": true,
    "data": {
        "id": 1,
        "name": "Example"
    }
}
Example Error Response
JSON
{
    "success": false,
    "message": "Unauthorized."
}
POST /sessions/{session}/resume Resume
Auth 60/minute

Submit a Sessions request to /sessions/{session}/resume.

Request Parameters
Parameter In Type Description
session *required path string Path parameter: Session
Example Request
JSON
[]
Example Success Response
JSON
{
    "success": true,
    "data": {
        "id": 1,
        "name": "Example"
    }
}
Example Error Response
JSON
{
    "success": false,
    "message": "Unauthorized."
}
GET /sessions/{session}/summary Cashier Session Summary
Auth 60/minute

Return the summary rollup for a cashier session.

Request Parameters
Parameter In Type Description
session *required path string Path parameter: Session
Example Request
JSON
[]
Example Success Response
JSON
{
    "success": true,
    "data": {
        "id": 1,
        "name": "Example"
    }
}
Example Error Response
JSON
{
    "success": false,
    "message": "Unauthorized."
}
POST /sessions/{session}/suspend Suspend
Auth 60/minute

Submit a Sessions request to /sessions/{session}/suspend.

Request Parameters
Parameter In Type Description
session *required path string Path parameter: Session
Example Request
JSON
[]
Example Success Response
JSON
{
    "success": true,
    "data": {
        "id": 1,
        "name": "Example"
    }
}
Example Error Response
JSON
{
    "success": false,
    "message": "Unauthorized."
}

Customers

Customer search, listing, and CRUD operations.

GET /customers List Customers
Auth 60/minute

Get paginated customers with status and search filters.

Example Request
JSON
[]
Example Success Response
JSON
{
    "success": true,
    "data": [
        {
            "id": 1,
            "name": "Example"
        }
    ]
}
Example Error Response
JSON
{
    "success": false,
    "message": "Unauthorized."
}
POST /customers Create Customers
Auth 60/minute

Submit a Customers request to /customers.

Request Parameters
Parameter In Type Description
name *required body string Field: Name
email body string Field: Email
phone body string Field: Phone
address body string Field: Address
customer_group_id body integer Field: Customer Group Id
Example Request
JSON
{
    "name": "example",
    "email": "user@example.com",
    "phone": "example",
    "address": "example",
    "customer_group_id": 1
}
Example Success Response
JSON
{
    "success": true,
    "data": [
        {
            "id": 1,
            "name": "Example"
        }
    ]
}
Example Error Response
JSON
{
    "success": false,
    "message": "Unauthorized."
}
GET /customers/search Search Customers
Auth 60/minute

Search customers by name, phone, email, or address for quick POS selection.

Example Request
JSON
[]
Example Success Response
JSON
{
    "success": true,
    "data": [
        {
            "id": 1,
            "name": "Example"
        }
    ]
}
Example Error Response
JSON
{
    "success": false,
    "message": "Unauthorized."
}
DELETE /customers/{customer} Delete Customers
Auth 60/minute

Delete Customers data through /customers/{customer}.

Request Parameters
Parameter In Type Description
customer *required path string Path parameter: Customer
Example Request
JSON
[]
Example Success Response
JSON
{
    "success": true,
    "message": "Operation completed successfully."
}
Example Error Response
JSON
{
    "success": false,
    "message": "Unauthorized."
}
GET /customers/{customer} Get Customers
Auth 60/minute

Retrieve Customers data from /customers/{customer}.

Request Parameters
Parameter In Type Description
customer *required path string Path parameter: Customer
Example Request
JSON
[]
Example Success Response
JSON
{
    "success": true,
    "data": {
        "id": 1,
        "name": "Example"
    }
}
Example Error Response
JSON
{
    "success": false,
    "message": "Unauthorized."
}
PUT /customers/{customer} Update Customers
Auth 60/minute

Update Customers data through /customers/{customer}.

Request Parameters
Parameter In Type Description
customer *required path string Path parameter: Customer
Example Request
JSON
[]
Example Success Response
JSON
{
    "success": true,
    "data": {
        "id": 1,
        "name": "Example"
    }
}
Example Error Response
JSON
{
    "success": false,
    "message": "Unauthorized."
}

Sales

Sales history, receipt, and search endpoints.

GET /sales List Sales
Auth 60/minute

Retrieve Sales data from /sales.

Example Request
JSON
[]
Example Success Response
JSON
{
    "success": true,
    "data": [
        {
            "id": 1,
            "name": "Example"
        }
    ]
}
Example Error Response
JSON
{
    "success": false,
    "message": "Unauthorized."
}
GET /sales/search Search Sales
Auth 60/minute

Retrieve Sales data from /sales/search.

Example Request
JSON
[]
Example Success Response
JSON
{
    "success": true,
    "data": [
        {
            "id": 1,
            "name": "Example"
        }
    ]
}
Example Error Response
JSON
{
    "success": false,
    "message": "Unauthorized."
}
GET /sales/{sale} Get Sales
Auth 60/minute

Retrieve Sales data from /sales/{sale}.

Request Parameters
Parameter In Type Description
sale *required path string Path parameter: Sale
Example Request
JSON
[]
Example Success Response
JSON
{
    "success": true,
    "data": {
        "id": 1,
        "name": "Example"
    }
}
Example Error Response
JSON
{
    "success": false,
    "message": "Unauthorized."
}
GET /sales/{sale}/receipt Receipt
Auth 60/minute

Retrieve Sales data from /sales/{sale}/receipt.

Request Parameters
Parameter In Type Description
sale *required path string Path parameter: Sale
Example Request
JSON
[]
Example Success Response
JSON
{
    "success": true,
    "data": {
        "id": 1,
        "name": "Example"
    }
}
Example Error Response
JSON
{
    "success": false,
    "message": "Unauthorized."
}

Returns

Return validation, creation, approval, and lookup endpoints.

GET /returns List Returns
Auth 60/minute

Get paginated returns scoped by store, warehouse, status, date, and search filters.

Example Request
JSON
[]
Example Success Response
JSON
{
    "success": true,
    "data": [
        {
            "id": 1,
            "name": "Example"
        }
    ]
}
Example Error Response
JSON
{
    "success": false,
    "message": "Unauthorized."
}
POST /returns Create Returns
Auth 60/minute

Submit a Returns request to /returns.

Request Parameters
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
Example Request
JSON
{
    "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"
}
Example Success Response
JSON
{
    "success": true,
    "data": [
        {
            "id": 1,
            "name": "Example"
        }
    ]
}
Example Error Response
JSON
{
    "success": false,
    "message": "Unauthorized."
}
GET /returns/check-sale Check Return Eligibility
Auth 60/minute

Validate whether a sale can be returned and return returnable items when eligible.

Example Request
JSON
[]
Example Success Response
JSON
{
    "success": true,
    "data": {
        "id": 1,
        "name": "Example"
    }
}
Example Error Response
JSON
{
    "success": false,
    "message": "Unauthorized."
}
GET /returns/reasons List Return Reasons
Auth 60/minute

Return active return reasons for return creation flows.

Example Request
JSON
[]
Example Success Response
JSON
{
    "success": true,
    "data": [
        {
            "id": 1,
            "name": "Example"
        }
    ]
}
Example Error Response
JSON
{
    "success": false,
    "message": "Unauthorized."
}
GET /returns/{saleReturn} Get Returns
Auth 60/minute

Retrieve Returns data from /returns/{saleReturn}.

Request Parameters
Parameter In Type Description
saleReturn *required path string Path parameter: Sale Return
Example Request
JSON
[]
Example Success Response
JSON
{
    "success": true,
    "data": {
        "id": 1,
        "name": "Example"
    }
}
Example Error Response
JSON
{
    "success": false,
    "message": "Unauthorized."
}
POST /returns/{saleReturn}/approve Approve
Auth 60/minute

Submit a Returns request to /returns/{saleReturn}/approve.

Request Parameters
Parameter In Type Description
saleReturn *required path string Path parameter: Sale Return
Example Request
JSON
[]
Example Success Response
JSON
{
    "success": true,
    "data": {
        "id": 1,
        "name": "Example"
    }
}
Example Error Response
JSON
{
    "success": false,
    "message": "Unauthorized."
}
POST /returns/{saleReturn}/cancel Cancel
Auth 60/minute

Submit a Returns request to /returns/{saleReturn}/cancel.

Request Parameters
Parameter In Type Description
saleReturn *required path string Path parameter: Sale Return
Example Request
JSON
[]
Example Success Response
JSON
{
    "success": true,
    "data": {
        "id": 1,
        "name": "Example"
    }
}
Example Error Response
JSON
{
    "success": false,
    "message": "Unauthorized."
}
POST /returns/{saleReturn}/process Process
Auth 60/minute

Submit a Returns request to /returns/{saleReturn}/process.

Request Parameters
Parameter In Type Description
saleReturn *required path string Path parameter: Sale Return
Example Request
JSON
[]
Example Success Response
JSON
{
    "success": true,
    "data": {
        "id": 1,
        "name": "Example"
    }
}
Example Error Response
JSON
{
    "success": false,
    "message": "Unauthorized."
}
POST /returns/{saleReturn}/reject Reject
Auth 60/minute

Submit a Returns request to /returns/{saleReturn}/reject.

Request Parameters
Parameter In Type Description
saleReturn *required path string Path parameter: Sale Return
Example Request
JSON
[]
Example Success Response
JSON
{
    "success": true,
    "data": {
        "id": 1,
        "name": "Example"
    }
}
Example Error Response
JSON
{
    "success": false,
    "message": "Unauthorized."
}

Offline Sync

Initial sync, delta sync, upload, and sync status APIs.

GET /sync/delta Delta Offline Sync
Auth 60/minute

Return only records changed since the provided timestamp.

Example Request
JSON
[]
Example Success Response
JSON
{
    "success": true,
    "data": {
        "id": 1,
        "name": "Example"
    }
}
Example Error Response
JSON
{
    "success": false,
    "message": "Unauthorized."
}
GET /sync/initial Initial Offline Sync
Auth 60/minute

Return the full offline payload needed to bootstrap a device.

Example Request
JSON
[]
Example Success Response
JSON
{
    "success": true,
    "data": {
        "id": 1,
        "name": "Example"
    }
}
Example Error Response
JSON
{
    "success": false,
    "message": "Unauthorized."
}
GET /sync/status Offline Sync Status
Auth 60/minute

Return sync health, pending uploads, and related status metadata.

Example Request
JSON
[]
Example Success Response
JSON
{
    "success": true,
    "data": {
        "id": 1,
        "name": "Example"
    }
}
Example Error Response
JSON
{
    "success": false,
    "message": "Unauthorized."
}
POST /sync/upload Upload Offline Transactions
Auth 60/minute

Upload offline-created transactions in bulk for server reconciliation.

Request Parameters
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
Example Request
JSON
{
    "store_id": 1,
    "transactions": [],
    "transactions.*.idempotency_key": "example",
    "transactions.*.type": "example",
    "transactions.*.offline_created_at": "2026-03-16",
    "transactions.*.data": []
}
Example Success Response
JSON
{
    "success": true,
    "data": {
        "id": 1,
        "name": "Example"
    }
}
Example Error Response
JSON
{
    "success": false,
    "message": "Unauthorized."
}

Pricing & Bundles

Price books, pricing rules, pricing calculators, bundles, and currencies.

GET /bundles List Bundles
Auth 60/minute

Get paginated bundles with optional store and validity filters.

Example Request
JSON
[]
Example Success Response
JSON
{
    "success": true,
    "data": [
        {
            "id": 1,
            "name": "Example"
        }
    ]
}
Example Error Response
JSON
{
    "success": false,
    "message": "Unauthorized."
}
POST /bundles Create Bundles
Auth 60/minute

Submit a Bundles request to /bundles.

Example Request
JSON
[]
Example Success Response
JSON
{
    "success": true,
    "data": [
        {
            "id": 1,
            "name": "Example"
        }
    ]
}
Example Error Response
JSON
{
    "success": false,
    "message": "Unauthorized."
}
POST /bundles/calculate Calculate Bundles
Auth 60/minute

Submit a Bundles request to /bundles/calculate.

Example Request
JSON
[]
Example Success Response
JSON
{
    "success": true,
    "data": {
        "id": 1,
        "name": "Example"
    }
}
Example Error Response
JSON
{
    "success": false,
    "message": "Unauthorized."
}
DELETE /bundles/{bundle} Delete Bundles
Auth 60/minute

Delete Bundles data through /bundles/{bundle}.

Request Parameters
Parameter In Type Description
bundle *required path string Path parameter: Bundle
Example Request
JSON
[]
Example Success Response
JSON
{
    "success": true,
    "message": "Operation completed successfully."
}
Example Error Response
JSON
{
    "success": false,
    "message": "Unauthorized."
}
GET /bundles/{bundle} Get Bundles
Auth 60/minute

Retrieve Bundles data from /bundles/{bundle}.

Request Parameters
Parameter In Type Description
bundle *required path string Path parameter: Bundle
Example Request
JSON
[]
Example Success Response
JSON
{
    "success": true,
    "data": {
        "id": 1,
        "name": "Example"
    }
}
Example Error Response
JSON
{
    "success": false,
    "message": "Unauthorized."
}
PUT /bundles/{bundle} Update Bundles
Auth 60/minute

Update Bundles data through /bundles/{bundle}.

Request Parameters
Parameter In Type Description
bundle *required path string Path parameter: Bundle
Example Request
JSON
[]
Example Success Response
JSON
{
    "success": true,
    "data": {
        "id": 1,
        "name": "Example"
    }
}
Example Error Response
JSON
{
    "success": false,
    "message": "Unauthorized."
}
GET /bundles/{bundle}/availability Availability
Auth 60/minute

Retrieve Bundles data from /bundles/{bundle}/availability.

Request Parameters
Parameter In Type Description
bundle *required path string Path parameter: Bundle
Example Request
JSON
[]
Example Success Response
JSON
{
    "success": true,
    "data": {
        "id": 1,
        "name": "Example"
    }
}
Example Error Response
JSON
{
    "success": false,
    "message": "Unauthorized."
}
GET /currencies List Currencies
Auth 60/minute

Retrieve Currencies data from /currencies.

Example Request
JSON
[]
Example Success Response
JSON
{
    "success": true,
    "data": [
        {
            "id": 1,
            "name": "Example"
        }
    ]
}
Example Error Response
JSON
{
    "success": false,
    "message": "Unauthorized."
}
GET /currencies/convert Convert
Auth 60/minute

Retrieve Currencies data from /currencies/convert.

Example Request
JSON
[]
Example Success Response
JSON
{
    "success": true,
    "data": {
        "id": 1,
        "name": "Example"
    }
}
Example Error Response
JSON
{
    "success": false,
    "message": "Unauthorized."
}
GET /currencies/{currency} Get Currencies
Auth 60/minute

Retrieve Currencies data from /currencies/{currency}.

Request Parameters
Parameter In Type Description
currency *required path string Path parameter: Currency
Example Request
JSON
[]
Example Success Response
JSON
{
    "success": true,
    "data": {
        "id": 1,
        "name": "Example"
    }
}
Example Error Response
JSON
{
    "success": false,
    "message": "Unauthorized."
}
GET /pricing-rules List Pricing Rules
Auth 60/minute

Get paginated pricing rules with optional store, validity, and relationship filters.

Example Request
JSON
[]
Example Success Response
JSON
{
    "success": true,
    "data": [
        {
            "id": 1,
            "name": "Example"
        }
    ]
}
Example Error Response
JSON
{
    "success": false,
    "message": "Unauthorized."
}
POST /pricing-rules Create Pricing Rules
Auth 60/minute

Submit a Pricing Rules request to /pricing-rules.

Example Request
JSON
[]
Example Success Response
JSON
{
    "success": true,
    "data": [
        {
            "id": 1,
            "name": "Example"
        }
    ]
}
Example Error Response
JSON
{
    "success": false,
    "message": "Unauthorized."
}
POST /pricing-rules/evaluate Evaluate
Auth 60/minute

Submit a Pricing Rules request to /pricing-rules/evaluate.

Example Request
JSON
[]
Example Success Response
JSON
{
    "success": true,
    "data": {
        "id": 1,
        "name": "Example"
    }
}
Example Error Response
JSON
{
    "success": false,
    "message": "Unauthorized."
}
DELETE /pricing-rules/{pricingRule} Delete Pricing Rules
Auth 60/minute

Delete Pricing Rules data through /pricing-rules/{pricingRule}.

Request Parameters
Parameter In Type Description
pricingRule *required path string Path parameter: Pricing Rule
Example Request
JSON
[]
Example Success Response
JSON
{
    "success": true,
    "message": "Operation completed successfully."
}
Example Error Response
JSON
{
    "success": false,
    "message": "Unauthorized."
}
GET /pricing-rules/{pricingRule} Get Pricing Rules
Auth 60/minute

Retrieve Pricing Rules data from /pricing-rules/{pricingRule}.

Request Parameters
Parameter In Type Description
pricingRule *required path string Path parameter: Pricing Rule
Example Request
JSON
[]
Example Success Response
JSON
{
    "success": true,
    "data": {
        "id": 1,
        "name": "Example"
    }
}
Example Error Response
JSON
{
    "success": false,
    "message": "Unauthorized."
}
PUT /pricing-rules/{pricingRule} Update Pricing Rules
Auth 60/minute

Update Pricing Rules data through /pricing-rules/{pricingRule}.

Request Parameters
Parameter In Type Description
pricingRule *required path string Path parameter: Pricing Rule
Example Request
JSON
[]
Example Success Response
JSON
{
    "success": true,
    "data": {
        "id": 1,
        "name": "Example"
    }
}
Example Error Response
JSON
{
    "success": false,
    "message": "Unauthorized."
}
POST /pricing-rules/{pricingRule}/toggle Toggle Pricing Rules
Auth 60/minute

Submit a Pricing Rules request to /pricing-rules/{pricingRule}/toggle.

Request Parameters
Parameter In Type Description
pricingRule *required path string Path parameter: Pricing Rule
Example Request
JSON
[]
Example Success Response
JSON
{
    "success": true,
    "data": {
        "id": 1,
        "name": "Example"
    }
}
Example Error Response
JSON
{
    "success": false,
    "message": "Unauthorized."
}
POST /pricing/calculate Calculate Price
Auth 60/minute

Evaluate pricing rules for a single item request.

Example Request
JSON
[]
Example Success Response
JSON
{
    "success": true,
    "data": {
        "id": 1,
        "name": "Example"
    }
}
Example Error Response
JSON
{
    "success": false,
    "message": "Unauthorized."
}
POST /pricing/calculate-result Calculate Result
Auth 60/minute

Submit a Pricing request to /pricing/calculate-result.

Example Request
JSON
[]
Example Success Response
JSON
{
    "success": true,
    "data": {
        "id": 1,
        "name": "Example"
    }
}
Example Error Response
JSON
{
    "success": false,
    "message": "Unauthorized."
}
POST /pricing/cart Cart
Auth 60/minute

Submit a Pricing request to /pricing/cart.

Example Request
JSON
[]
Example Success Response
JSON
{
    "success": true,
    "data": {
        "id": 1,
        "name": "Example"
    }
}
Example Error Response
JSON
{
    "success": false,
    "message": "Unauthorized."
}
GET /pricing/rules Rules
Auth 60/minute

Retrieve Pricing data from /pricing/rules.

Example Request
JSON
[]
Example Success Response
JSON
{
    "success": true,
    "data": [
        {
            "id": 1,
            "name": "Example"
        }
    ]
}
Example Error Response
JSON
{
    "success": false,
    "message": "Unauthorized."
}
GET /pricing/rules-for-item/{item} Rules For Item
Auth 60/minute

Retrieve Pricing data from /pricing/rules-for-item/{item}.

Request Parameters
Parameter In Type Description
item *required path string Path parameter: Item
Example Request
JSON
[]
Example Success Response
JSON
{
    "success": true,
    "data": {
        "id": 1,
        "name": "Example"
    }
}
Example Error Response
JSON
{
    "success": false,
    "message": "Unauthorized."
}
POST /pricing/validate Validate
Auth 60/minute

Submit a Pricing request to /pricing/validate.

Example Request
JSON
[]
Example Success Response
JSON
{
    "success": true,
    "data": {
        "id": 1,
        "name": "Example"
    }
}
Example Error Response
JSON
{
    "success": false,
    "message": "Unauthorized."
}

Reporting

Read-only integration sales and report feeds for external consumers.

GET /integration/reports/cashier Cashier
Auth 60/minute

Retrieve Integration Reports data from /integration/reports/cashier.

Example Request
JSON
[]
Example Success Response
JSON
{
    "success": true,
    "data": {
        "id": 1,
        "name": "Example"
    }
}
Example Error Response
JSON
{
    "success": false,
    "message": "Unauthorized."
}
GET /integration/reports/cashier/{cashier} Detail
Auth 60/minute

Retrieve Integration Reports Cashier data from /integration/reports/cashier/{cashier}.

Request Parameters
Parameter In Type Description
cashier *required path string Path parameter: Cashier
Example Request
JSON
[]
Example Success Response
JSON
{
    "success": true,
    "data": {
        "id": 1,
        "name": "Example"
    }
}
Example Error Response
JSON
{
    "success": false,
    "message": "Unauthorized."
}
GET /integration/reports/closeout Closeout
Auth 60/minute

Retrieve Integration Reports data from /integration/reports/closeout.

Example Request
JSON
[]
Example Success Response
JSON
{
    "success": true,
    "data": {
        "id": 1,
        "name": "Example"
    }
}
Example Error Response
JSON
{
    "success": false,
    "message": "Unauthorized."
}
GET /integration/reports/customers Customers
Auth 60/minute

Retrieve Integration Reports data from /integration/reports/customers.

Example Request
JSON
[]
Example Success Response
JSON
{
    "success": true,
    "data": [
        {
            "id": 1,
            "name": "Example"
        }
    ]
}
Example Error Response
JSON
{
    "success": false,
    "message": "Unauthorized."
}
GET /integration/reports/discounts Discounts
Auth 60/minute

Retrieve Integration Reports data from /integration/reports/discounts.

Example Request
JSON
[]
Example Success Response
JSON
{
    "success": true,
    "data": [
        {
            "id": 1,
            "name": "Example"
        }
    ]
}
Example Error Response
JSON
{
    "success": false,
    "message": "Unauthorized."
}
GET /integration/reports/heatmap Heatmap
Auth 60/minute

Retrieve Integration Reports data from /integration/reports/heatmap.

Example Request
JSON
[]
Example Success Response
JSON
{
    "success": true,
    "data": {
        "id": 1,
        "name": "Example"
    }
}
Example Error Response
JSON
{
    "success": false,
    "message": "Unauthorized."
}
GET /integration/reports/inventory/damage-spoilage Damage Spoilage
Auth 60/minute

Retrieve Integration Reports Inventory data from /integration/reports/inventory/damage-spoilage.

Example Request
JSON
[]
Example Success Response
JSON
{
    "success": true,
    "data": {
        "id": 1,
        "name": "Example"
    }
}
Example Error Response
JSON
{
    "success": false,
    "message": "Unauthorized."
}
GET /integration/reports/inventory/dead-stock Dead Stock
Auth 60/minute

Retrieve Integration Reports Inventory data from /integration/reports/inventory/dead-stock.

Example Request
JSON
[]
Example Success Response
JSON
{
    "success": true,
    "data": {
        "id": 1,
        "name": "Example"
    }
}
Example Error Response
JSON
{
    "success": false,
    "message": "Unauthorized."
}
GET /integration/reports/inventory/movement Movement
Auth 60/minute

Retrieve Integration Reports Inventory data from /integration/reports/inventory/movement.

Example Request
JSON
[]
Example Success Response
JSON
{
    "success": true,
    "data": {
        "id": 1,
        "name": "Example"
    }
}
Example Error Response
JSON
{
    "success": false,
    "message": "Unauthorized."
}
GET /integration/reports/inventory/overview Overview
Auth 60/minute

Retrieve Integration Reports Inventory data from /integration/reports/inventory/overview.

Example Request
JSON
[]
Example Success Response
JSON
{
    "success": true,
    "data": {
        "id": 1,
        "name": "Example"
    }
}
Example Error Response
JSON
{
    "success": false,
    "message": "Unauthorized."
}
GET /integration/reports/inventory/replenishment Replenishment
Auth 60/minute

Retrieve Integration Reports Inventory data from /integration/reports/inventory/replenishment.

Example Request
JSON
[]
Example Success Response
JSON
{
    "success": true,
    "data": {
        "id": 1,
        "name": "Example"
    }
}
Example Error Response
JSON
{
    "success": false,
    "message": "Unauthorized."
}
GET /integration/reports/inventory/stock Stock
Auth 60/minute

Retrieve Integration Reports Inventory data from /integration/reports/inventory/stock.

Example Request
JSON
[]
Example Success Response
JSON
{
    "success": true,
    "data": {
        "id": 1,
        "name": "Example"
    }
}
Example Error Response
JSON
{
    "success": false,
    "message": "Unauthorized."
}
GET /integration/reports/inventory/turns Turns
Auth 60/minute

Retrieve Integration Reports Inventory data from /integration/reports/inventory/turns.

Example Request
JSON
[]
Example Success Response
JSON
{
    "success": true,
    "data": [
        {
            "id": 1,
            "name": "Example"
        }
    ]
}
Example Error Response
JSON
{
    "success": false,
    "message": "Unauthorized."
}
GET /integration/reports/inventory/valuation Valuation
Auth 60/minute

Retrieve Integration Reports Inventory data from /integration/reports/inventory/valuation.

Example Request
JSON
[]
Example Success Response
JSON
{
    "success": true,
    "data": {
        "id": 1,
        "name": "Example"
    }
}
Example Error Response
JSON
{
    "success": false,
    "message": "Unauthorized."
}
GET /integration/reports/invoices Invoices
Auth 60/minute

Retrieve Integration Reports data from /integration/reports/invoices.

Example Request
JSON
[]
Example Success Response
JSON
{
    "success": true,
    "data": [
        {
            "id": 1,
            "name": "Example"
        }
    ]
}
Example Error Response
JSON
{
    "success": false,
    "message": "Unauthorized."
}
GET /integration/reports/invoices/aging Aging
Auth 60/minute

Retrieve Integration Reports Invoices data from /integration/reports/invoices/aging.

Example Request
JSON
[]
Example Success Response
JSON
{
    "success": true,
    "data": {
        "id": 1,
        "name": "Example"
    }
}
Example Error Response
JSON
{
    "success": false,
    "message": "Unauthorized."
}
GET /integration/reports/invoices/credit-notes Credit Notes
Auth 60/minute

Retrieve Integration Reports Invoices data from /integration/reports/invoices/credit-notes.

Example Request
JSON
[]
Example Success Response
JSON
{
    "success": true,
    "data": [
        {
            "id": 1,
            "name": "Example"
        }
    ]
}
Example Error Response
JSON
{
    "success": false,
    "message": "Unauthorized."
}
GET /integration/reports/invoices/dso Dso
Auth 60/minute

Retrieve Integration Reports Invoices data from /integration/reports/invoices/dso.

Example Request
JSON
[]
Example Success Response
JSON
{
    "success": true,
    "data": {
        "id": 1,
        "name": "Example"
    }
}
Example Error Response
JSON
{
    "success": false,
    "message": "Unauthorized."
}
GET /integration/reports/invoices/funnel Funnel
Auth 60/minute

Retrieve Integration Reports Invoices data from /integration/reports/invoices/funnel.

Example Request
JSON
[]
Example Success Response
JSON
{
    "success": true,
    "data": {
        "id": 1,
        "name": "Example"
    }
}
Example Error Response
JSON
{
    "success": false,
    "message": "Unauthorized."
}
GET /integration/reports/overview Integration Report Overview
Auth 60/minute

Return high-level report overview data for external consumers.

Example Request
JSON
[]
Example Success Response
JSON
{
    "success": true,
    "data": {
        "id": 1,
        "name": "Example"
    }
}
Example Error Response
JSON
{
    "success": false,
    "message": "Unauthorized."
}
GET /integration/reports/payments Payments
Auth 60/minute

Retrieve Integration Reports data from /integration/reports/payments.

Example Request
JSON
[]
Example Success Response
JSON
{
    "success": true,
    "data": [
        {
            "id": 1,
            "name": "Example"
        }
    ]
}
Example Error Response
JSON
{
    "success": false,
    "message": "Unauthorized."
}
GET /integration/reports/price-source Price Source
Auth 60/minute

Retrieve Integration Reports data from /integration/reports/price-source.

Example Request
JSON
[]
Example Success Response
JSON
{
    "success": true,
    "data": {
        "id": 1,
        "name": "Example"
    }
}
Example Error Response
JSON
{
    "success": false,
    "message": "Unauthorized."
}
GET /integration/reports/products Products
Auth 60/minute

Retrieve Integration Reports data from /integration/reports/products.

Example Request
JSON
[]
Example Success Response
JSON
{
    "success": true,
    "data": [
        {
            "id": 1,
            "name": "Example"
        }
    ]
}
Example Error Response
JSON
{
    "success": false,
    "message": "Unauthorized."
}
GET /integration/reports/products/abc Abc
Auth 60/minute

Retrieve Integration Reports Products data from /integration/reports/products/abc.

Example Request
JSON
[]
Example Success Response
JSON
{
    "success": true,
    "data": {
        "id": 1,
        "name": "Example"
    }
}
Example Error Response
JSON
{
    "success": false,
    "message": "Unauthorized."
}
GET /integration/reports/profit Profit
Auth 60/minute

Retrieve Integration Reports data from /integration/reports/profit.

Example Request
JSON
[]
Example Success Response
JSON
{
    "success": true,
    "data": {
        "id": 1,
        "name": "Example"
    }
}
Example Error Response
JSON
{
    "success": false,
    "message": "Unauthorized."
}
GET /integration/reports/return-rate Return Rate
Auth 60/minute

Retrieve Integration Reports data from /integration/reports/return-rate.

Example Request
JSON
[]
Example Success Response
JSON
{
    "success": true,
    "data": {
        "id": 1,
        "name": "Example"
    }
}
Example Error Response
JSON
{
    "success": false,
    "message": "Unauthorized."
}
GET /integration/reports/returns Returns
Auth 60/minute

Retrieve Integration Reports data from /integration/reports/returns.

Example Request
JSON
[]
Example Success Response
JSON
{
    "success": true,
    "data": [
        {
            "id": 1,
            "name": "Example"
        }
    ]
}
Example Error Response
JSON
{
    "success": false,
    "message": "Unauthorized."
}
GET /integration/reports/revenue Revenue
Auth 60/minute

Retrieve Integration Reports data from /integration/reports/revenue.

Example Request
JSON
[]
Example Success Response
JSON
{
    "success": true,
    "data": {
        "id": 1,
        "name": "Example"
    }
}
Example Error Response
JSON
{
    "success": false,
    "message": "Unauthorized."
}
GET /integration/reports/sales Integration Report Sales
Auth 60/minute

Return detailed sales reporting rows for external consumers.

Example Request
JSON
[]
Example Success Response
JSON
{
    "success": true,
    "data": [
        {
            "id": 1,
            "name": "Example"
        }
    ]
}
Example Error Response
JSON
{
    "success": false,
    "message": "Unauthorized."
}
GET /integration/reports/stores Stores
Auth 60/minute

Retrieve Integration Reports data from /integration/reports/stores.

Example Request
JSON
[]
Example Success Response
JSON
{
    "success": true,
    "data": [
        {
            "id": 1,
            "name": "Example"
        }
    ]
}
Example Error Response
JSON
{
    "success": false,
    "message": "Unauthorized."
}
GET /integration/reports/tax Tax
Auth 60/minute

Retrieve Integration Reports data from /integration/reports/tax.

Example Request
JSON
[]
Example Success Response
JSON
{
    "success": true,
    "data": {
        "id": 1,
        "name": "Example"
    }
}
Example Error Response
JSON
{
    "success": false,
    "message": "Unauthorized."
}
GET /integration/reports/voids Voids
Auth 60/minute

Retrieve Integration Reports data from /integration/reports/voids.

Example Request
JSON
[]
Example Success Response
JSON
{
    "success": true,
    "data": [
        {
            "id": 1,
            "name": "Example"
        }
    ]
}
Example Error Response
JSON
{
    "success": false,
    "message": "Unauthorized."
}
GET /integration/sales Daily Summary Pull (By Status)
Auth 60/minute

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.

Request Parameters
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)
Example Request
JSON
{
    "from": "2026-02-11",
    "to": "2026-02-11",
    "status": "completed",
    "store_id": 1,
    "per_page": 100
}
Example Success Response
JSON
{
    "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
    }
}
Example Error Response
JSON
{
    "success": false,
    "message": "Unauthorized."
}
GET /integration/sales/{sale} Incremental Sync Drilldown
Auth 60/minute

Use sale detail lookups to reconcile line items and payments flagged during reporting syncs. Compatibility alias: `/integrations/sales/{sale}`.

Request Parameters
Parameter In Type Description
sale *required path string Path parameter: Sale
Example Request
JSON
[]
Example Success Response
JSON
{
    "success": true,
    "data": {
        "id": 1,
        "name": "Example"
    }
}
Example Error Response
JSON
{
    "success": false,
    "message": "Unauthorized."
}

Integration Data

Read-only integration access to items, customers, stores, warehouses, and related data feeds.

GET /integration/data/customers Integration Data: Customers
Auth 60/minute

Return paginated customer data for external integrations.

Example Request
JSON
[]
Example Success Response
JSON
{
    "success": true,
    "data": [
        {
            "id": 1,
            "name": "Example"
        }
    ]
}
Example Error Response
JSON
{
    "success": false,
    "message": "Unauthorized."
}
GET /integration/data/customers/{customer} Get Integration Data Customers
Auth 60/minute

Retrieve Integration Data Customers data from /integration/data/customers/{customer}.

Request Parameters
Parameter In Type Description
customer *required path string Path parameter: Customer
Example Request
JSON
[]
Example Success Response
JSON
{
    "success": true,
    "data": {
        "id": 1,
        "name": "Example"
    }
}
Example Error Response
JSON
{
    "success": false,
    "message": "Unauthorized."
}
GET /integration/data/inventory List Integration Data Inventory
Auth 60/minute

Retrieve Integration Data Inventory data from /integration/data/inventory.

Example Request
JSON
[]
Example Success Response
JSON
{
    "success": true,
    "data": [
        {
            "id": 1,
            "name": "Example"
        }
    ]
}
Example Error Response
JSON
{
    "success": false,
    "message": "Unauthorized."
}
GET /integration/data/invoices List Integration Data Invoices
Auth 60/minute

Retrieve Integration Data Invoices data from /integration/data/invoices.

Example Request
JSON
[]
Example Success Response
JSON
{
    "success": true,
    "data": [
        {
            "id": 1,
            "name": "Example"
        }
    ]
}
Example Error Response
JSON
{
    "success": false,
    "message": "Unauthorized."
}
GET /integration/data/invoices/{invoice} Get Integration Data Invoices
Auth 60/minute

Retrieve Integration Data Invoices data from /integration/data/invoices/{invoice}.

Request Parameters
Parameter In Type Description
invoice *required path string Path parameter: Invoice
Example Request
JSON
[]
Example Success Response
JSON
{
    "success": true,
    "data": {
        "id": 1,
        "name": "Example"
    }
}
Example Error Response
JSON
{
    "success": false,
    "message": "Unauthorized."
}
GET /integration/data/items Integration Data: Items
Auth 60/minute

Return paginated item data for external integrations.

Example Request
JSON
[]
Example Success Response
JSON
{
    "success": true,
    "data": [
        {
            "id": 1,
            "name": "Example"
        }
    ]
}
Example Error Response
JSON
{
    "success": false,
    "message": "Unauthorized."
}
GET /integration/data/returns List Integration Data Returns
Auth 60/minute

Retrieve Integration Data Returns data from /integration/data/returns.

Example Request
JSON
[]
Example Success Response
JSON
{
    "success": true,
    "data": [
        {
            "id": 1,
            "name": "Example"
        }
    ]
}
Example Error Response
JSON
{
    "success": false,
    "message": "Unauthorized."
}
GET /integration/data/returns/{saleReturn} Get Integration Data Returns
Auth 60/minute

Retrieve Integration Data Returns data from /integration/data/returns/{saleReturn}.

Request Parameters
Parameter In Type Description
saleReturn *required path string Path parameter: Sale Return
Example Request
JSON
[]
Example Success Response
JSON
{
    "success": true,
    "data": {
        "id": 1,
        "name": "Example"
    }
}
Example Error Response
JSON
{
    "success": false,
    "message": "Unauthorized."
}
GET /integration/data/sales List Integration Data Sales
Auth 60/minute

Retrieve Integration Data Sales data from /integration/data/sales.

Example Request
JSON
[]
Example Success Response
JSON
{
    "success": true,
    "data": [
        {
            "id": 1,
            "name": "Example"
        }
    ]
}
Example Error Response
JSON
{
    "success": false,
    "message": "Unauthorized."
}
GET /integration/data/sales/{sale} Get Integration Data Sales
Auth 60/minute

Retrieve Integration Data Sales data from /integration/data/sales/{sale}.

Request Parameters
Parameter In Type Description
sale *required path string Path parameter: Sale
Example Request
JSON
[]
Example Success Response
JSON
{
    "success": true,
    "data": {
        "id": 1,
        "name": "Example"
    }
}
Example Error Response
JSON
{
    "success": false,
    "message": "Unauthorized."
}
GET /integration/data/stores Integration Data: Stores
Auth 60/minute

Return store reference data for external integrations.

Example Request
JSON
[]
Example Success Response
JSON
{
    "success": true,
    "data": [
        {
            "id": 1,
            "name": "Example"
        }
    ]
}
Example Error Response
JSON
{
    "success": false,
    "message": "Unauthorized."
}
GET /integration/data/stores/{store} Get Integration Data Stores
Auth 60/minute

Retrieve Integration Data Stores data from /integration/data/stores/{store}.

Request Parameters
Parameter In Type Description
store *required path string Path parameter: Store
Example Request
JSON
[]
Example Success Response
JSON
{
    "success": true,
    "data": {
        "id": 1,
        "name": "Example"
    }
}
Example Error Response
JSON
{
    "success": false,
    "message": "Unauthorized."
}
GET /integration/data/warehouses Integration Data: Warehouses
Auth 60/minute

Return warehouse reference data for external integrations.

Example Request
JSON
[]
Example Success Response
JSON
{
    "success": true,
    "data": [
        {
            "id": 1,
            "name": "Example"
        }
    ]
}
Example Error Response
JSON
{
    "success": false,
    "message": "Unauthorized."
}
GET /integration/data/warehouses/{warehouse} Get Integration Data Warehouses
Auth 60/minute

Retrieve Integration Data Warehouses data from /integration/data/warehouses/{warehouse}.

Request Parameters
Parameter In Type Description
warehouse *required path string Path parameter: Warehouse
Example Request
JSON
[]
Example Success Response
JSON
{
    "success": true,
    "data": {
        "id": 1,
        "name": "Example"
    }
}
Example Error Response
JSON
{
    "success": false,
    "message": "Unauthorized."
}

Other

Additional authenticated API endpoints.

POST /prices/bulk Bulk
Auth 60/minute

Submit a Prices request to /prices/bulk.

Example Request
JSON
[]
Example Success Response
JSON
{
    "success": true,
    "data": {
        "id": 1,
        "name": "Example"
    }
}
Example Error Response
JSON
{
    "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:

422 Validation Error
{
    "success": false,
    "message": "The given data was invalid.",
    "errors": {
        "email": ["The email field is required."],
        "password": ["The password must be at least 8 characters."]
    }
}