| Property | Value |
|---|---|
| Base URL | /api |
| Protocol | HTTPS (enforced in production) |
| Database | PostgreSQL |
| ERP Integration | SAP Business One (Service Layer) |
| Fiscal Integration | REVMax |
| Payment Gateways | PayNow, Innbucks, Ecocash |
| API Format | JSON |
- Current default API version:
1.0 - Base URL remains:
/api - If no API version is supplied, the server uses version
1.0 - Clients can request a specific API version with the
X-API-Versionheader or theapi-versionquery string - Breaking contract changes must be introduced in a new API version; existing version
1.0endpoints remain supported for current clients
Examples:
GET /api/HealthGET /api/Health?api-version=1.0X-API-Version: 1.0
- Authentication
- Authorization & Permissions
- Rate Limiting
- Security Headers & Middleware
- Idempotency
- Common Response Patterns
- Endpoints
- Auth
- Password Management
- Two-Factor Authentication
- Users
- User Management
- User Activity
- Products
- Stock
- Prices
- Invoices
- Credit Notes
- Sales Orders
- Quotations
- Purchase Orders
- Incoming Payments
- Payment Gateways
- Inventory Transfers
- Business Partners
- Exchange Rates
- GL Accounts
- Cost Centres
- Documents
- Reports
- Statements
- Notifications
- Webhooks
- Backups
- Rate Limit Management
- SAP Settings
- Desktop Integration
- Customer Portal
- REVMax Proxy
- Health
- DTOs Reference
The API supports two authentication methods:
Include the token in the Authorization header:
Authorization: Bearer <access_token>
| Setting | Value |
|---|---|
| Issuer | ShopInventoryAPI |
| Audience | ShopInventoryClients |
| Access Token TTL | 60 minutes |
| Refresh Token TTL | 7 days |
Login Flow:
POST /api/Auth/loginwith username/password- If 2FA is enabled, response includes
RequiresTwoFactor: trueand aTwoFactorToken - Re-submit login with the 2FA code and token
- On success, receive
AccessToken+RefreshToken - Use
POST /api/Auth/refreshbefore the access token expires
Include the key in the X-API-Key header:
X-API-Key: <api_key>
API keys are configured server-side with assigned roles and optional expiration dates.
Users are assigned one role: Admin, Manager, User, Cashier, StockController, DepotController, PodOperator, or ApiUser.
| Policy | Required Roles |
|---|---|
AdminOnly |
Admin |
ApiAccess |
Admin, ApiUser, User, Cashier, StockController, DepotController, Manager, PodOperator |
Endpoints may require specific permissions checked via the [RequirePermission] attribute:
| Category | Permissions |
|---|---|
| Dashboard | dashboard.view |
| Products | products.view, products.create, products.edit, products.delete, products.manage_prices |
| Invoices | invoices.view, invoices.create, invoices.edit, invoices.delete, invoices.void |
| Purchasing | purchasing.view, purchasing.create, purchasing.edit, purchasing.delete, purchasing.approve, purchasing.receive |
| Payments | payments.view, payments.create, payments.refund, payments.process_refunds |
| Inventory | stock.view, stock.edit, stock.transfer, stock.adjust, inventory.view, inventory.transfer, inventory.adjust |
| Reports | reports.view, reports.export |
| Customers | customers.view, customers.create, customers.edit, customers.delete |
| Users | users.view, users.create, users.edit, users.delete, users.manage_roles, users.manage_permissions |
| Settings | settings.view, settings.edit, settings.manage, settings.integrations |
| Audit | audit.view, audit.export |
| Webhooks | webhooks.view, webhooks.manage |
| System | sync.view, sync.manage, system.admin, backups.view, backups.create, backups.restore, backups.delete |
| Scope | Limit | Window |
|---|---|---|
| Global | 100 requests | 60 seconds |
| Auth endpoints | 10 requests | 60 seconds |
| Queue limit | 10 | — |
When rate-limited, the API returns HTTP 429 Too Many Requests with a Retry-After header.
All responses include:
| Header | Value |
|---|---|
X-Frame-Options |
DENY |
X-Content-Type-Options |
nosniff |
X-XSS-Protection |
1; mode=block |
Content-Security-Policy |
Strict (relaxed for Swagger UI) |
Cache-Control |
no-store, no-cache for mutating requests; public, max-age=300 for GET requests |
For POST/PUT operations on critical endpoints, include an Idempotency-Key header to prevent duplicate submissions:
Idempotency-Key: <unique-uuid>
Supported endpoints:
/api/Invoice/api/SalesOrder/api/CreditNote/api/IncomingPayment/api/InventoryTransfer/api/Payment
Idempotency keys expire after 60 minutes.
{
"page": 1,
"pageSize": 20,
"totalCount": 150,
"totalPages": 8,
"hasMore": true,
"items": [ ... ]
}{
"error": "Description of what went wrong",
"details": "Additional context (optional)"
}{
"type": "https://tools.ietf.org/html/rfc9110#section-15.5.1",
"title": "One or more validation errors occurred.",
"errors": {
"FieldName": ["Error message"]
}
}Base route: /api/Auth
Login with username and password.
- Auth: None (Anonymous)
- Request Body:
{
"username": "string",
"password": "string"
}- Response (200):
{
"accessToken": "eyJhbG...",
"refreshToken": "abc123...",
"expiresAt": "2026-04-01T13:00:00Z",
"tokenType": "Bearer",
"user": {
"username": "admin",
"role": "Admin",
"email": "admin@example.com",
"assignedWarehouseCode": "WH01",
"assignedWarehouseCodes": ["WH01", "WH02"],
"allowedPaymentMethods": ["Cash", "Transfer"]
}
}- Response (2FA required):
{
"requiresTwoFactor": true,
"twoFactorToken": "temp-token..."
}Exchange a refresh token for a new access token.
- Auth: None (Anonymous)
- Request Body:
{
"refreshToken": "abc123..."
}- Response (200): Same as login response.
Revoke the current refresh token.
- Auth: Bearer
- Request Body:
{
"refreshToken": "abc123..."
}Get the current authenticated user's info.
- Auth: Bearer
- Response (200):
UserInfoobject.
Register a new user (Admin only).
- Auth: Bearer + Admin role
- Request Body:
{
"username": "string",
"email": "string",
"password": "string",
"role": "string"
}Base route: /api/Password
Request a password reset email.
- Auth: None (Anonymous)
- Request Body:
{
"email": "user@example.com"
}Validate a password reset token.
- Auth: None (Anonymous)
Complete the password reset.
- Auth: None (Anonymous)
- Request Body:
{
"token": "string",
"newPassword": "string",
"confirmPassword": "string"
}Change the current user's password.
- Auth: Bearer
- Request Body:
{
"currentPassword": "string",
"newPassword": "string",
"confirmPassword": "string"
}Base route: /api/TwoFactor
| Method | Endpoint | Description |
|---|---|---|
| GET | /api/TwoFactor/status |
Get current 2FA status for the authenticated user |
| POST | /api/TwoFactor/setup |
Initiate 2FA setup, returns secret key + QR code URI |
| POST | /api/TwoFactor/verify |
Verify TOTP code and enable 2FA |
| POST | /api/TwoFactor/disable |
Disable 2FA |
All endpoints require Bearer authentication.
Setup Response:
{
"secretKey": "BASE32SECRET",
"qrCodeUri": "otpauth://totp/ShopInventory:user?secret=...",
"manualEntryKey": "XXXX XXXX XXXX XXXX",
"backupCodes": ["code1", "code2", "..."]
}Base route: /api/User
Auth: Bearer + Admin role
| Method | Endpoint | Description |
|---|---|---|
| GET | /api/User |
List all users (paginated, searchable) |
| GET | /api/User/{id} |
Get user by ID |
| PUT | /api/User/{id} |
Update user details |
| POST | /api/User/{id}/change-password |
Admin-initiated password change |
| POST | /api/User/{id}/unlock |
Unlock a locked-out account |
GET list query parameters: page, pageSize, search
User DTO:
{
"id": 1,
"username": "jdoe",
"email": "jdoe@example.com",
"role": "Cashier",
"firstName": "John",
"lastName": "Doe",
"isActive": true,
"emailVerified": true,
"failedLoginAttempts": 0,
"lockoutEnd": null,
"createdAt": "2026-01-01T00:00:00Z",
"lastLoginAt": "2026-04-01T08:30:00Z",
"assignedWarehouseCodes": ["WH01"]
}Base route: /api/UserManagement
Auth: Bearer + specific permissions per endpoint
| Method | Endpoint | Permission | Description |
|---|---|---|---|
| GET | /api/UserManagement |
users.view |
List users with full details |
| GET | /api/UserManagement/{id} |
users.view |
Get user with permissions |
| POST | /api/UserManagement |
users.create |
Create user with granular permissions |
| PUT | /api/UserManagement/{id} |
users.edit |
Update user + permissions |
| DELETE | /api/UserManagement/{id} |
users.delete |
Delete user |
Create User Request:
{
"username": "string",
"email": "string",
"password": "string",
"firstName": "string",
"lastName": "string",
"role": "Cashier",
"permissions": ["invoices.view", "invoices.create", "payments.view"],
"assignedWarehouseCodes": ["WH01", "WH02"],
"allowedPaymentMethods": ["Cash", "Transfer", "EcoCash"],
"sendWelcomeEmail": true
}User Detail Response:
{
"id": 1,
"username": "jdoe",
"email": "jdoe@example.com",
"firstName": "John",
"lastName": "Doe",
"role": "Cashier",
"isActive": true,
"emailVerified": true,
"twoFactorEnabled": false,
"isLockedOut": false,
"lockoutEnd": null,
"permissions": ["invoices.view", "invoices.create"],
"assignedWarehouseCodes": ["WH01"],
"allowedPaymentMethods": ["Cash", "Transfer"],
"createdAt": "2026-01-01T00:00:00Z",
"updatedAt": "2026-03-15T10:00:00Z",
"lastLoginAt": "2026-04-01T08:30:00Z"
}Base route: /api/UserActivity
| Method | Endpoint | Permission | Description |
|---|---|---|---|
| GET | /api/UserActivity/dashboard |
audit.view |
System-wide activity dashboard |
| GET | /api/UserActivity/user/{userId} |
audit.view |
Specific user's activity summary |
| GET | /api/UserActivity/me |
ApiAccess | Current user's own activity |
Dashboard Response:
{
"fromDate": "2026-03-01",
"toDate": "2026-04-01",
"totalUsers": 25,
"activeUsers": 18,
"totalLogins": 450,
"failedLogins": 12,
"totalActions": 3200,
"activityByUser": [...],
"activityByType": [...],
"hourlyActivity": [...]
}Base route: /api/Product
Auth: Bearer + ApiAccess
| Method | Endpoint | Description |
|---|---|---|
| GET | /api/Product |
Get all products from SAP |
| GET | /api/Product/warehouse/{warehouseCode} |
Products in a specific warehouse with batch info |
| GET | /api/Product/{itemCode} |
Get a single product |
| GET | /api/Product/{itemCode}/batches |
Get batch information for a product |
Product DTO:
{
"itemCode": "PRD001",
"itemName": "Widget A",
"barCode": "1234567890",
"itemType": "itItems",
"managesBatches": true,
"quantityInStock": 150.0,
"quantityAvailable": 120.0,
"quantityCommitted": 30.0,
"price": 25.99,
"defaultWarehouse": "WH01",
"uoM": "Each",
"batches": [
{
"batchNumber": "B2026-001",
"quantity": 80.0,
"status": "Released",
"expiryDate": "2027-06-01",
"admissionDate": "2026-01-15"
}
]
}Base route: /api/Stock
Auth: Bearer + ApiAccess
| Method | Endpoint | Description |
|---|---|---|
| GET | /api/Stock/warehouses |
Get all warehouses (cached 5 min) |
| GET | /api/Stock/warehouse-codes |
Get just warehouse codes |
| GET | /api/Stock/warehouse/{code} |
Get all stock in a specific warehouse |
| GET | /api/Stock/batch/{warehouseCode}/{itemCode} |
Get batch detail for an item in a warehouse |
Warehouse DTO:
{
"warehouseCode": "WH01",
"warehouseName": "Main Warehouse",
"location": "Harare",
"street": "123 Industrial Rd",
"city": "Harare",
"country": "ZW",
"isActive": true
}Stock Quantity DTO:
{
"itemCode": "PRD001",
"itemName": "Widget A",
"barCode": "1234567890",
"warehouseCode": "WH01",
"inStock": 150.0,
"committed": 30.0,
"ordered": 50.0,
"available": 120.0,
"uoM": "Each",
"packagingCode": "PKG001",
"packagingMaterialStock": 500,
"packagingLabelStock": 1200,
"packagingLidStock": 800
}Base route: /api/Price
Auth: Bearer + ApiAccess
| Method | Endpoint | Description |
|---|---|---|
| GET | /api/Price/cached |
Get cached prices (synced every 5 minutes) |
| GET | /api/Price |
Get all prices directly from SAP |
| GET | /api/Price/by-customer/{cardCode} |
Customer-specific pricing |
| POST | /api/Price/sync |
Force price sync from SAP (Admin only) |
Price DTO:
{
"itemCode": "PRD001",
"itemName": "Widget A",
"price": 25.99,
"currency": "USD",
"priceListNum": 1,
"priceListName": "Base Price List"
}Base route: /api/Invoice
Auth: Bearer + permissions as noted
| Method | Endpoint | Permission | Description |
|---|---|---|---|
| POST | /api/Invoice |
Admin or Cashier role | Create a new invoice |
| GET | /api/Invoice |
invoices.view |
List invoices (paginated) |
| GET | /api/Invoice/{id} |
invoices.view |
Get invoice by ID |
| GET | /api/Invoice/docnum/{docNum} |
invoices.view |
Get invoice by SAP DocNum |
| GET | /api/Invoice/date-range |
invoices.view |
Get invoices by date range |
| PUT | /api/Invoice/{id} |
invoices.edit |
Update a draft invoice |
| DELETE | /api/Invoice/{id} |
invoices.delete |
Delete a draft invoice |
| POST | /api/Invoice/confirm |
invoices.create |
Confirm and post invoice to SAP |
Query parameters for GET list: page, pageSize, status, cardCode, fromDate, toDate, warehouseCode
Create Invoice Request:
{
"cardCode": "C0001",
"docDate": "2026-04-01",
"docDueDate": "2026-04-30",
"numAtCard": "PO-12345",
"comments": "Standard order",
"docCurrency": "USD",
"salesPersonCode": 1,
"u_Van_saleorder": "VSO-001",
"lines": [
{
"itemCode": "PRD001",
"quantity": 10,
"unitPrice": 25.99,
"warehouseCode": "WH01",
"taxCode": "X1",
"discountPercent": 0,
"uoMCode": "Each",
"uoMEntry": 1
}
]
}Additional query parameters for creation:
autoAllocateBatches(bool, default:true) — auto-allocate batch numbers for batch-managed itemsallocationStrategy(FEFO|FIFO) — batch allocation strategywarehouseCode— required for batch-managed items
Invoice Response:
{
"docEntry": 12345,
"docNum": 1001,
"docDate": "2026-04-01",
"docDueDate": "2026-04-30",
"cardCode": "C0001",
"cardName": "ABC Trading",
"numAtCard": "PO-12345",
"comments": "Standard order",
"docStatus": "Open",
"docTotal": 259.90,
"paidToDate": 0,
"vatSum": 40.28,
"docCurrency": "USD",
"customerVatNo": "VAT123456",
"customerTinNumber": "TIN789",
"lines": [
{
"lineNum": 0,
"itemCode": "PRD001",
"itemDescription": "Widget A",
"quantity": 10,
"unitPrice": 25.99,
"lineTotal": 259.90,
"warehouseCode": "WH01",
"discountPercent": 0
}
]
}Note: Invoice creation performs stock validation, batch allocation (FEFO/FIFO), stock locking, SAP posting, and optional REVMax fiscalization in a single transaction.
Base route: /api/CreditNote
Auth: Bearer + permissions as noted
| Method | Endpoint | Permission | Description |
|---|---|---|---|
| GET | /api/CreditNote |
invoices.view |
List credit notes (paginated) |
| GET | /api/CreditNote/{id} |
invoices.view |
Get by ID |
| GET | /api/CreditNote/number/{creditNoteNumber} |
invoices.view |
Get by credit note number |
| GET | /api/CreditNote/by-invoice/{invoiceId} |
invoices.view |
Credit notes for an invoice |
| POST | /api/CreditNote |
invoices.create |
Create credit note |
Query parameters: page, pageSize, status, cardCode, fromDate, toDate
Credit Note Types: Return, Adjustment, Damage
Credit Note Statuses: Draft, Pending, Approved, Cancelled, Applied
Create Credit Note Request:
{
"cardCode": "C0001",
"cardName": "ABC Trading",
"type": "Return",
"originalInvoiceId": 1,
"originalInvoiceDocEntry": 12345,
"reason": "Damaged goods returned",
"comments": "",
"currency": "USD",
"restockItems": true,
"restockWarehouseCode": "WH01",
"lines": [
{
"itemCode": "PRD001",
"itemDescription": "Widget A",
"quantity": 2,
"unitPrice": 25.99,
"discountPercent": 0,
"taxPercent": 15.5,
"warehouseCode": "WH01",
"returnReason": "Damaged in transit",
"batchNumber": "B2026-001"
}
]
}Base route: /api/SalesOrder
Auth: Bearer + permissions as noted
| Method | Endpoint | Permission | Description |
|---|---|---|---|
| GET | /api/SalesOrder |
invoices.view |
List local sales orders |
| GET | /api/SalesOrder/{id} |
invoices.view |
Get by ID |
| GET | /api/SalesOrder/number/{orderNumber} |
invoices.view |
Get by order number |
| POST | /api/SalesOrder |
invoices.create |
Create sales order |
| PUT | /api/SalesOrder/{id} |
invoices.edit |
Update sales order |
Sales Order Statuses: Draft, Pending, Approved, PartiallyInvoiced, Invoiced, Cancelled
Create Sales Order Request:
{
"deliveryDate": "2026-04-15",
"cardCode": "C0001",
"cardName": "ABC Trading",
"customerRefNo": "REF-001",
"comments": "",
"salesPersonCode": 1,
"salesPersonName": "John Sales",
"currency": "USD",
"discountPercent": 5,
"shipToAddress": "123 Delivery St",
"billToAddress": "456 Billing Ave",
"warehouseCode": "WH01",
"lines": [
{
"itemCode": "PRD001",
"itemDescription": "Widget A",
"quantity": 50,
"unitPrice": 25.99,
"discountPercent": 0,
"taxPercent": 15.5,
"warehouseCode": "WH01",
"uoMCode": "Each"
}
]
}Base route: /api/Quotation
Auth: Bearer + permissions as noted
| Method | Endpoint | Permission | Description |
|---|---|---|---|
| GET | /api/Quotation |
invoices.view |
List local quotations |
| GET | /api/Quotation/sap |
invoices.view |
List SAP quotations |
| GET | /api/Quotation/{id} |
invoices.view |
Get by ID |
| POST | /api/Quotation |
invoices.create |
Create quotation |
| PUT | /api/Quotation/{id} |
invoices.edit |
Update quotation |
Quotation Statuses: Draft, Pending, Approved, Converted, Expired, Cancelled
Create Quotation Request:
{
"validUntil": "2026-05-01",
"cardCode": "C0001",
"cardName": "ABC Trading",
"customerRefNo": "RFQ-001",
"contactPerson": "Jane Buyer",
"comments": "",
"termsAndConditions": "Payment within 30 days",
"salesPersonCode": 1,
"currency": "USD",
"discountPercent": 0,
"warehouseCode": "WH01",
"lines": [
{
"itemCode": "PRD001",
"itemDescription": "Widget A",
"quantity": 100,
"unitPrice": 24.99,
"discountPercent": 5,
"taxPercent": 15.5,
"warehouseCode": "WH01"
}
]
}Base route: /api/PurchaseOrder
Auth: Bearer + purchasing permissions
| Method | Endpoint | Permission | Description |
|---|---|---|---|
| GET | /api/PurchaseOrder |
purchasing.view |
List local purchase orders |
| GET | /api/PurchaseOrder/sap |
purchasing.view |
List SAP purchase orders |
| GET | /api/PurchaseOrder/{id} |
purchasing.view |
Get by ID |
| POST | /api/PurchaseOrder |
purchasing.create |
Create purchase order |
| PUT | /api/PurchaseOrder/{id} |
purchasing.edit |
Update purchase order |
| POST | /api/PurchaseOrder/{id}/receive |
purchasing.receive |
Receive goods |
Purchase Order Statuses: Draft, Pending, Approved, PartiallyReceived, Received, Cancelled, OnHold
Receive Goods Request:
{
"comments": "Received at dock 3",
"warehouseCode": "WH01",
"lines": [
{
"lineNum": 0,
"itemCode": "PRD001",
"quantityReceived": 45,
"warehouseCode": "WH01",
"batchNumber": "B2026-005"
}
]
}Base route: /api/IncomingPayment
Auth: Bearer + ApiAccess
| Method | Endpoint | Description |
|---|---|---|
| POST | /api/IncomingPayment |
Create incoming payment (posts to SAP) |
| GET | /api/IncomingPayment/{docEntry} |
Get by SAP DocEntry |
| GET | /api/IncomingPayment/docnum/{docNum} |
Get by document number |
| GET | /api/IncomingPayment/by-invoice/{invoiceDocEntry} |
Payments for an invoice |
| GET | /api/IncomingPayment/customer/{cardCode} |
Customer's payments (paginated) |
Incoming Payment DTO:
{
"docEntry": 5001,
"docNum": 2001,
"docDate": "2026-04-01",
"docDueDate": "2026-04-01",
"cardCode": "C0001",
"cardName": "ABC Trading",
"docCurrency": "USD",
"cashSum": 0,
"checkSum": 0,
"transferSum": 259.90,
"creditSum": 0,
"docTotal": 259.90,
"remarks": "Bank transfer payment",
"transferReference": "TRF-2026-001",
"transferDate": "2026-04-01",
"transferAccount": "_SYS00000000089",
"paymentInvoices": [
{
"lineNum": 0,
"docEntry": 12345,
"sumApplied": 259.90,
"invoiceType": "it_Invoice"
}
]
}Base route: /api/Payment
Auth: Bearer + ApiAccess
| Method | Endpoint | Description |
|---|---|---|
| GET | /api/Payment/providers |
Get available payment providers |
| POST | /api/Payment/initiate |
Initiate a payment transaction |
| GET | /api/Payment/{id}/status |
Check payment status |
| GET | /api/Payment/transactions |
Transaction history (paginated, filterable) |
Supported Providers: PayNow, Innbucks, Ecocash
Initiate Payment Request:
{
"provider": "Ecocash",
"amount": 259.90,
"currency": "USD",
"phoneNumber": "+263771234567",
"email": "customer@example.com",
"invoiceId": "INV-1001",
"customerCode": "C0001",
"reference": "Payment for INV-1001",
"returnUrl": "https://app.example.com/payment/complete",
"callbackUrl": "https://api.example.com/api/Payment/callback"
}Initiate Payment Response:
{
"transactionId": "txn-uuid-here",
"externalTransactionId": "ECO-12345",
"status": "Pending",
"provider": "Ecocash",
"paymentUrl": null,
"qrCode": null,
"ussdCode": "*151*2*1*amount#",
"instructions": "Approve the payment on your phone",
"expiresAt": "2026-04-01T12:30:00Z"
}Payment Statuses: Pending, Processing, Success, Failed, Cancelled, Refunded, Expired
Base route: /api/InventoryTransfer
Auth: Bearer + ApiAccess
| Method | Endpoint | Description |
|---|---|---|
| POST | /api/InventoryTransfer |
Create an inventory transfer between warehouses |
| GET | /api/InventoryTransfer/{docEntry} |
Get transfer details |
| GET | /api/InventoryTransfer/status/{docEntry} |
Get posting status |
Create Transfer Request:
{
"fromWarehouse": "WH01",
"toWarehouse": "WH02",
"docDate": "2026-04-01",
"dueDate": "2026-04-05",
"comments": "Restocking branch warehouse",
"lines": [
{
"itemCode": "PRD001",
"quantity": 20,
"fromWarehouseCode": "WH01",
"toWarehouseCode": "WH02"
}
]
}Base route: /api/BusinessPartner
Auth: Bearer + ApiAccess
| Method | Endpoint | Description |
|---|---|---|
| GET | /api/BusinessPartner |
Get all business partners from SAP |
| GET | /api/BusinessPartner/type/{cardType} |
Filter by type |
| GET | /api/BusinessPartner/search?query={query} |
Search by code or name |
| GET | /api/BusinessPartner/{cardCode} |
Get specific business partner |
Card Types: cCustomer, cSupplier, cLead
Business Partner DTO:
{
"cardCode": "C0001",
"cardName": "ABC Trading",
"cardType": "cCustomer",
"groupCode": 100,
"phone1": "+263771234567",
"phone2": null,
"email": "accounts@abc.co.zw",
"address": "123 Main St",
"city": "Harare",
"country": "ZW",
"currency": "USD",
"balance": 1500.00,
"isActive": true,
"priceListNum": 1,
"priceListName": "Base Price",
"vatRegNo": "VAT123456",
"tinNumber": "TIN789012"
}Base route: /api/ExchangeRate
Auth: Bearer + ApiAccess
| Method | Endpoint | Description |
|---|---|---|
| GET | /api/ExchangeRate |
Get all active exchange rates |
| GET | /api/ExchangeRate/{fromCurrency}/{toCurrency} |
Current rate between two currencies |
| GET | /api/ExchangeRate/{fromCurrency}/{toCurrency}/history?days={days} |
Rate history (default 30 days) |
| GET | /api/ExchangeRate/convert?from={from}&to={to}&amount={amount} |
Convert an amount |
Exchange Rate DTO:
{
"id": 1,
"fromCurrency": "USD",
"toCurrency": "ZIG",
"rate": 25.75,
"inverseRate": 0.0388,
"effectiveDate": "2026-04-01",
"source": "RBZ",
"isActive": true,
"createdAt": "2026-04-01T08:00:00Z"
}Base route: /api/GLAccount
Auth: Bearer + ApiAccess
| Method | Endpoint | Description |
|---|---|---|
| GET | /api/GLAccount |
Get all G/L accounts from SAP |
| GET | /api/GLAccount/type/{accountType} |
Filter by type |
| GET | /api/GLAccount/{accountCode} |
Get specific account |
Account Types: at_Revenues, at_Expenses, at_Other
Base route: /api/CostCentre
Auth: Bearer + ApiAccess
| Method | Endpoint | Description |
|---|---|---|
| GET | /api/CostCentre |
Get all active cost centres from SAP |
| GET | /api/CostCentre/dimension/{dimension} |
Filter by dimension (1-5) |
| GET | /api/CostCentre/{centerCode} |
Get specific cost centre |
Cost Centre DTO:
{
"centerCode": "CC001",
"centerName": "Head Office",
"dimension": 1,
"isActive": true,
"validFrom": "2025-01-01",
"validTo": null,
"displayName": "CC001 - Head Office"
}Base route: /api/Document
Auth: Bearer + ApiAccess (Admin/Manager for create/update)
| Method | Endpoint | Description |
|---|---|---|
| GET | /api/Document/templates |
List all templates (filter by ?type=Invoice) |
| GET | /api/Document/templates/{id} |
Get template by ID |
| GET | /api/Document/templates/default/{documentType} |
Get default template for a document type |
| POST | /api/Document/templates |
Create template (Admin/Manager) |
| PUT | /api/Document/templates/{id} |
Update template |
Document Types: Invoice, CreditNote, SalesOrder, Quotation, PurchaseOrder, Statement, DeliveryNote
Template DTO:
{
"id": 1,
"name": "Standard Invoice",
"documentType": "Invoice",
"htmlContent": "<html>...</html>",
"cssStyles": "body { font-family: sans-serif; }",
"headerContent": "<div>Company Logo</div>",
"footerContent": "<div>Terms & Conditions</div>",
"paperSize": "A4",
"orientation": "Portrait",
"isDefault": true,
"isActive": true
}| Method | Endpoint | Description |
|---|---|---|
| GET | /api/Document/attachments/{entityType}/{entityId} |
List attachments for an entity |
| POST | /api/Document/attachments |
Upload attachment (multipart form) |
| DELETE | /api/Document/attachments/{attachmentId} |
Delete attachment |
Base route: /api/Report
Auth: Bearer + reports.view permission
Cache: All report endpoints are cached for 15 minutes (900 seconds)
| Method | Endpoint | Description |
|---|---|---|
| GET | /api/Report/sales-summary |
Sales summary for a date range |
| GET | /api/Report/top-products |
Top selling products |
| GET | /api/Report/low-stock |
Low stock alert items |
| GET | /api/Report/aging-analysis |
Customer aging analysis |
| GET | /api/Report/inventory-value |
Inventory valuation report |
Common query parameters: fromDate, toDate, warehouseCode, top (for top N)
Sales Summary Response:
{
"totalInvoices": 245,
"totalSalesUSD": 125000.50,
"totalSalesZIG": 3218762.88,
"totalVatUSD": 19375.08,
"totalVatZIG": 498908.25,
"averageInvoiceValueUSD": 510.20,
"averageInvoiceValueZIG": 13138.22,
"uniqueCustomers": 48,
"dailySales": [
{
"date": "2026-04-01",
"invoiceCount": 12,
"totalSalesUSD": 5230.50,
"totalSalesZIG": 134685.38
}
],
"salesByCurrency": [
{
"currency": "USD",
"invoiceCount": 180,
"totalSales": 125000.50,
"totalVat": 19375.08
}
]
}Low Stock Alert Response:
{
"reportDate": "2026-04-01",
"totalAlerts": 15,
"criticalCount": 3,
"warningCount": 12,
"items": [
{
"itemCode": "PRD005",
"itemName": "Widget E",
"warehouseCode": "WH01",
"currentStock": 5,
"reorderLevel": 50,
"minimumStock": 10,
"alertLevel": "Critical",
"suggestedReorderQty": 100
}
]
}Base route: /api/Statement
Auth: Bearer + ApiAccess
Generate a customer account statement PDF.
Query parameters: fromDate, toDate
Response: PDF file download or statement data with aging buckets.
Base route: /api/Notification
Auth: Bearer + ApiAccess
| Method | Endpoint | Description |
|---|---|---|
| GET | /api/Notification |
Get notifications (paginated, filterable by type/read status) |
| GET | /api/Notification/unread-count |
Get unread notification count |
| POST | /api/Notification/mark-read |
Mark notifications as read |
| POST | /api/Notification |
Create notification (Admin only) |
| DELETE | /api/Notification/{id} |
Delete a notification |
Notification Types: Info, Warning, Error, Success, Alert
Notification Categories: LowStock, Payment, Invoice, System
Create Notification Request:
{
"title": "Low Stock Alert",
"message": "Widget A is below reorder level in WH01",
"type": "Warning",
"category": "LowStock",
"entityType": "Product",
"entityId": "PRD001",
"actionUrl": "/products/PRD001",
"targetUsername": null,
"targetRole": "StockController"
}Mark Read Request:
{
"notificationIds": [1, 2, 3]
}Pass null for notificationIds to mark all as read.
Base route: /api/Webhook
Auth: Bearer + Admin role
| Method | Endpoint | Description |
|---|---|---|
| GET | /api/Webhook |
List all webhooks |
| GET | /api/Webhook/{id} |
Get webhook details |
| POST | /api/Webhook |
Create webhook subscription |
| PUT | /api/Webhook/{id} |
Update webhook |
| DELETE | /api/Webhook/{id} |
Delete webhook |
| POST | /api/Webhook/{id}/test |
Send test event to webhook |
Supported Event Types:
| Category | Events |
|---|---|
| Invoice | invoice.created, invoice.paid, invoice.cancelled |
| Payment | payment.received, payment.failed, payment.refunded |
| Stock | stock.low, stock.out, stock.replenished, stock.transfer |
| Inventory | inventory.adjusted, inventory.received |
| Customer | customer.created, customer.updated |
| SAP | sap.sync.success, sap.sync.failed, sap.connection.lost, sap.connection.restored |
Create Webhook Request:
{
"name": "Stock Alerts",
"url": "https://hooks.example.com/stock",
"secret": "whsec_abc123",
"events": ["stock.low", "stock.out"],
"retryCount": 3,
"timeoutSeconds": 30,
"customHeaders": {
"X-Custom-Header": "value"
}
}Webhook Delivery Payload:
{
"event": "stock.low",
"timestamp": "2026-04-01T10:30:00Z",
"data": { ... },
"signature": "sha256=..."
}The signature is an HMAC-SHA256 of the payload body using the webhook secret.
Base route: /api/Backup
Auth: Bearer + backup permissions
| Method | Endpoint | Permission | Description |
|---|---|---|---|
| GET | /api/Backup |
backups.view |
List all backups (paginated) |
| GET | /api/Backup/{id} |
backups.view |
Get backup details |
| GET | /api/Backup/stats |
backups.view |
Backup statistics |
| POST | /api/Backup |
backups.create |
Create new backup |
Create Backup Request:
{
"backupType": "Full",
"description": "Pre-deployment backup",
"uploadToCloud": false
}Backup Stats Response:
{
"totalBackups": 45,
"successfulBackups": 43,
"failedBackups": 2,
"totalSizeBytes": 1073741824,
"totalSizeFormatted": "1.00 GB",
"lastBackupAt": "2026-04-01T06:00:00Z",
"nextScheduledBackup": "2026-04-02T06:00:00Z",
"backupsLast24Hours": 2,
"backupsLast7Days": 14
}Base route: /api/RateLimit
Auth: Bearer + permissions as noted
| Method | Endpoint | Permission | Description |
|---|---|---|---|
| GET | /api/RateLimit |
users.edit |
List all rate limits |
| GET | /api/RateLimit/client/{clientId} |
users.edit |
Get client's rate limit info |
| GET | /api/RateLimit/current |
ApiAccess | Get current request's rate limit status |
| GET | /api/RateLimit/check |
ApiAccess | Check if request would be allowed (non-incrementing) |
Rate Limit Status Response:
{
"clientId": "user:admin",
"requestsInWindow": 45,
"maxRequests": 100,
"windowSizeSeconds": 60,
"windowResetAt": "2026-04-01T10:31:00Z",
"isBlocked": false,
"blockedUntil": null,
"remainingRequests": 55
}Base route: /api/sap-settings
Auth: Bearer + Admin role
| Method | Endpoint | Description |
|---|---|---|
| GET | /api/sap-settings |
Get current SAP settings (password masked) |
| PUT | /api/sap-settings |
Update SAP connection settings |
| POST | /api/sap-settings/test-connection |
Test SAP connectivity |
Update SAP Settings Request:
{
"serviceLayerUrl": "https://sap-server:50000/b1s/v1",
"companyDB": "SBO_Production",
"userName": "manager",
"password": "new-password",
"testConnection": true
}Connection Test Response:
{
"success": true,
"message": "Connected successfully",
"responseTimeMs": 245,
"testedAt": "2026-04-01T10:00:00Z"
}Base route: /api/DesktopIntegration
Auth: Bearer + ApiAccess
This controller supports stock reservations and queue-based invoice posting for the desktop application.
| Method | Endpoint | Description |
|---|---|---|
| POST | /api/DesktopIntegration/reservations |
Create stock reservation (holds inventory) |
| GET | /api/DesktopIntegration/reservations/{reservationId} |
Get reservation details |
| PUT | /api/DesktopIntegration/reservations/{reservationId}/confirm |
Confirm and post to SAP |
| DELETE | /api/DesktopIntegration/reservations/{reservationId} |
Cancel reservation |
Create Reservation Request:
{
"externalReferenceId": "DESKTOP-INV-001",
"externalReference": "Desktop Invoice",
"sourceSystem": "DesktopApp",
"documentType": "Invoice",
"cardCode": "C0001",
"cardName": "ABC Trading",
"currency": "USD",
"reservationDurationMinutes": 60,
"requiresFiscalization": true,
"priority": 1,
"notes": "",
"lines": [
{
"lineNum": 0,
"itemCode": "PRD001",
"itemDescription": "Widget A",
"quantity": 10,
"uoMCode": "Each",
"warehouseCode": "WH01",
"unitPrice": 25.99,
"taxCode": "X1",
"autoAllocateBatches": true
}
]
}Note: Reservations hold physical stock for the specified duration (default 60 minutes). Stock is released if the reservation is not confirmed before expiry.
| Method | Endpoint | Description |
|---|---|---|
| POST | /api/DesktopIntegration/invoices/queue |
Queue invoice for async posting |
| GET | /api/DesktopIntegration/invoices/queue-status/{queueId} |
Check queue status |
| Method | Endpoint | Description |
|---|---|---|
| POST | /api/DesktopIntegration/validate-batch-allocation |
Validate batch allocations (FIFO/FEFO) |
Base route: /api/CustomerPortal
Auth: Bearer + Admin role
| Method | Endpoint | Description |
|---|---|---|
| POST | /api/CustomerPortal/register |
Register a customer portal user |
| POST | /api/CustomerPortal/generate-hash |
Generate a BCrypt password hash |
| POST | /api/CustomerPortal/bulk-register |
Bulk register from SAP business partners |
Base route: /api/revmax
Auth: Bearer + ApiAccess
Proxy endpoints for the REVMax fiscal device (ZIMRA compliance).
| Method | Endpoint | Description |
|---|---|---|
| GET | /api/revmax/card-details |
Fiscal device card details (TIN, BPN, serial number) |
| GET | /api/revmax/day-status |
Current fiscal day status |
| GET | /api/revmax/license |
Get the active REVMax license |
| POST | /api/revmax/license |
Set or replace the active REVMax license |
| GET | /api/revmax/z-report |
Get fiscal report (Z-report) |
| GET | /api/revmax/invoices/{invoiceNumber} |
Get a fiscalized invoice by invoice number |
| GET | /api/revmax/unprocessed-invoices/summary |
Get the summary of invoices still pending processing on REVMax |
| POST | /api/revmax/transact |
Submit a standard fiscal transaction |
| POST | /api/revmax/transact-ext |
Submit an extended fiscal transaction used to fiscalise credit notes where the original invoice was fiscalised on a different device |
/api/revmax/transact-ext proxies REVMax TransactMExt and extends the standard transaction payload with refDeviceId, refReceiptGlobalNo, and refFiscalDayNo. Use it when the credit note must reference a prior fiscalized invoice from a different device.
VAT Rate: 15.5% (configurable)
Fiscal Response:
{
"code": 0,
"message": "Success",
"qrCode": "https://fdms.zimra.co.zw/...",
"verificationCode": "ABC123",
"verificationLink": "https://fdms.zimra.co.zw/verify/...",
"deviceID": "DEV001",
"deviceSerialNumber": "SN123456",
"fiscalDay": 245,
"data": {
"receiptGlobalNo": 1234,
"receiptCounter": 567,
"fiscalDayNo": 245,
"invoiceNo": "INV-1001"
}
}Base route: /api/Health
Health check endpoint. No authentication required.
Response (200):
{
"status": "Healthy",
"timestamp": "2026-04-01T10:00:00Z"
}Used when creating invoices with batch-managed items.
Batch Allocation Request:
{
"lines": [
{
"lineNumber": 0,
"itemCode": "PRD001",
"warehouseCode": "WH01",
"quantity": 10,
"uoMCode": "Each",
"batchAllocations": [
{ "batchNumber": "B2026-001", "quantity": 6 },
{ "batchNumber": "B2026-002", "quantity": 4 }
]
}
],
"autoAllocate": true,
"strategy": "FEFO"
}Allocation Strategies:
FEFO— First Expiry, First Out (default for perishable goods)FIFO— First In, First Out (by admission date)Manual— Client specifies exact batch allocations
Batch Allocation Result:
{
"isValid": true,
"validationErrors": [],
"warnings": [],
"allocatedLines": [
{
"lineNumber": 0,
"itemCode": "PRD001",
"warehouseCode": "WH01",
"totalQuantityAllocated": 10,
"batches": [
{
"batchNumber": "B2026-001",
"quantityAllocated": 6,
"availableBeforeAllocation": 80,
"remainingAfterAllocation": 74,
"expiryDate": "2027-01-15",
"allocationOrder": 1
},
{
"batchNumber": "B2026-002",
"quantityAllocated": 4,
"availableBeforeAllocation": 50,
"remainingAfterAllocation": 46,
"expiryDate": "2027-06-01",
"allocationOrder": 2
}
]
}
],
"totalLinesValidated": 1,
"linesPassedValidation": 1,
"batchesAutoAllocated": 2,
"strategyUsed": "FEFO"
}Returned when stock is insufficient for an operation:
{
"lineNumber": 0,
"itemCode": "PRD001",
"itemName": "Widget A",
"warehouseCode": "WH01",
"requestedQuantity": 100,
"availableQuantity": 45,
"shortage": 55,
"batchNumber": null,
"message": "Insufficient stock: requested 100, available 45"
}{
"eventType": "stock.low",
"category": "Stock",
"description": "Triggered when stock falls below reorder level"
}{
"isConnected": true,
"status": "Connected",
"lastConnectedAt": "2026-04-01T10:00:00Z",
"lastErrorAt": null,
"lastError": null,
"consecutiveFailures": 0,
"responseTimeMs": 120,
"sapVersion": "10.0",
"companyDb": "SBO_Production"
}| HTTP Status | Meaning |
|---|---|
| 200 | Success |
| 201 | Created |
| 400 | Bad Request / Validation Error |
| 401 | Unauthorized (missing or invalid token) |
| 403 | Forbidden (insufficient permissions) |
| 404 | Resource Not Found |
| 409 | Conflict (duplicate idempotency key or concurrency issue) |
| 429 | Too Many Requests (rate limited) |
| 500 | Internal Server Error |
| 502 | SAP Service Layer Unavailable |
| 503 | Service Unavailable |
The system operates with dual currencies:
| Code | Name |
|---|---|
USD |
United States Dollar |
ZIG |
Zimbabwe Gold |
Exchange rates are managed via the /api/ExchangeRate endpoints and are used for currency conversion across all financial documents.