Sales Orders – OptoSoft API
Search sales orders with paging, read a single order and its line items, list orders carrying an outstanding balance, change order status and cancel an order with a reason.
10 endpoints in this module. Every request needs a JWT bearer token — see Authentication.
Sales Orders
A sales order is the central transactional record in OptoSoft: what the customer ordered, what it costs, when it is due for delivery, how much has been paid, and where it currently sits in the fulfilment workflow.
In an optical store an order usually cannot be delivered immediately — lenses go to a lab for surfacing and fitting. The order therefore moves through a status lifecycle and accrues receipts (advances and final payments) along the way. These endpoints let you read that state and drive it forward.
Order paths are not consistently typed. Most take an
encrypted string ID, but /api/Orders/{salesOrderId}/items
takes a numeric int64. Others key on the
human-readable salesOrderNo rather than an ID at
all. Each endpoint below states which it expects — check before wiring it up.
Searching orders
POST /api/Orders/search
Bearer token
The primary order-listing endpoint. Every field is an optional filter, and paging
uses the pageNumber /
pageSize style. This is the endpoint to poll
on a date range if you need to detect new orders.
Request body
| Field | Type | Required | Description |
|---|---|---|---|
| pageNumber | int32 | Required | 1-based page index. |
| pageSize | int32 | Required | Rows per page. Keep to 50 or below. |
| yearID | int64 | Optional | Financial year. Strongly recommended — without it you scan every year. |
| customerID | string | Optional | Encrypted customer ID, to list one customer’s orders. |
| salesOrderNo | string | Optional | Order number, exact or partial. |
| customerName | string | Optional | Partial match on customer name. |
| customerMobile | string | Optional | Customer mobile number. |
| branchName | string | Optional | Filter a multi-store chain by branch name. |
| date | string | Optional | Single order date. |
| fromDate | string | Optional | Start of a date range. Use with toDate. |
| toDate | string | Optional | End of a date range. |
| status | string | Optional | Order status filter. |
| acceptedBy | string | Optional | Staff member who took the order. |
| deliveryMode | string | Optional | Collection or delivery mode. |
| deliveryDateTime | string | Optional | Promised delivery date. |
| netAmount | string | Optional | Exact net amount. |
| discount | string | Optional | Discount amount. |
| orderBy | string | Optional | Sort column, optionally with DESC. |
Example request
curl -X POST https://api.opto-soft.com/api/Orders/search \
-H "Authorization: Bearer YOUR_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"pageNumber": 1,
"pageSize": 25,
"yearID": 9,
"fromDate": "2026-07-01",
"toDate": "2026-07-26",
"orderBy": "Date DESC"
}'
Response
A paged envelope: Items plus
Total, PageNumber
and PageSize. Note the response fields are
PascalCase, unlike the camelCase request body above.
| Field | Type | Description |
|---|---|---|
| Items[].ID | string | Encrypted sales order ID. Use this on follow-up calls. |
| Items[].SalesOrderNo | string | Human-readable order number shown to the customer. |
| Items[].CustomerName | string | Customer name. |
| Items[].CustomerMobileNo | string | Customer mobile number. |
| Items[].BranchName | string | Originating store. |
| Items[].Date | date-time | Order date. |
| Items[].AcceptedBy | string | Staff member who took the order. |
| Items[].DeliveryModeName | string | Delivery or collection mode. |
| Items[].DeliveryDateTime | string | Promised delivery date. |
| Items[].NetAmount | number | Order value after discount and tax. |
| Items[].StatusID | integer | Numeric status. Pass this to the status-change endpoint. |
| Items[].Status | string | Display label for the status. |
| Items[].ItemCount | integer | Number of line items. |
| Items[].TotalReceipt | number | Amount received so far. Balance is NetAmount - TotalReceipt. |
| Items[].TotalDiscount | number | Total discount applied. |
| Total | integer | Total matching orders before paging. |
| PageNumber | integer | Echo of the requested page index. |
| PageSize | integer | Echo of the requested page size. |
Example response — 200 OK
{
"Items": [
{
"ID": "Q2ZQdGhSZVhBaVlXTnpaWFF3TWk0d01B",
"CustomerName": "Rajesh Sharma",
"CustomerMobileNo": "9876543210",
"BranchName": "Main Branch",
"SalesOrderNo": "SO-2024-0568",
"Date": "2024-01-15T00:00:00",
"AcceptedBy": "John Doe",
"DeliveryModeName": "Store Pickup",
"DeliveryDateTime": "22/01/2024 10:00 AM",
"NetAmount": 8500.00,
"StatusID": 2,
"Status": "Processing",
"ItemCount": 3,
"TotalReceipt": 3000.00,
"TotalDiscount": 1200.00
}
],
"Total": 245,
"PageNumber": 1,
"PageSize": 20
}
Reading a single order
GET /api/Orders/{salesOrderNo}
Bearer tokenFetches a complete order by its human-readable order number — not by ID. This is what you call when a customer quotes their order number at the counter or over the phone.
Path parameters
| Name | Type | Required | Description |
|---|---|---|---|
| salesOrderNo | string | Required | Order number as printed on the customer’s copy. URL-encode any slashes. |
Example request
curl "https://api.opto-soft.com/api/Orders/SO%2F2026%2F001842" \
-H "Authorization: Bearer YOUR_TOKEN"
Errors
| Status | Cause |
|---|---|
| 404 | No order with that number in the token’s company and branch scope. |
| 500 | Server error. Retry with backoff. |
Example response — 200 OK
{
"SalesOrderID": "Q2ZQdGhSZVhBaVlXTnpaWFF3TWk0d01B",
"SalesOrderNO": "SO-2024-0568",
"SalseOrderDate": "2024-01-15T00:00:00",
"DeliveryDate": "2024-01-22T00:00:00",
"CustomerID": "Q2ZQdGhSZVhBaVlXTnpaWFF4TWk0d01B",
"CustomerFullName": "Rajesh Sharma",
"HouseNumber": "A-12",
"SocietyName": "Green Gardens",
"Street": "MG Road",
"Area": "Fort",
"Landmark": "Near Post Office",
"EmailId": "rajesh.sharma@email.com",
"CustomerMobile": "9876543210",
"CustomerGSTNO": null,
"BranchID": "Q2ZQdGhSZVhBaVlXTnpaWFF5TWk0d01B",
"BranchName": "Main Branch",
"BranchAddress": "123 MG Road",
"BranchCity": "Mumbai",
"BrachEmailId": "main@visionoptics.com",
"BranchMobile": "9876500000",
"CompanyName": "Vision Optics Pvt Ltd",
"GSTNO": "27ABCDE1234F1Z5",
"IsCompositionScheme": false,
"SalesRepresentativeID": "Q2ZQdGhSZVhBaVlXTnpaWFF6TWk0d01B",
"SRName": "Amit Kumar",
"YearID": "Q2ZQdGhSZVhBaVlXTnpaWFF6T0M0d01B",
"YearName": "2024-2025",
"TaxTypeID": "1",
"TaxType": "CGST + SGST",
"StatusID": "2",
"Transport": null,
"TotalQuantity": 3.00,
"ITotalPrice": 7200.00,
"TotalDiscount": 1200.00,
"TotalTaxableAmount": 6000.00,
"TotalSGST": 540.00,
"TotalCGST": 540.00,
"TotalIGST": 0.00,
"JobCharges": 200.00,
"ServiceTax": 0.00,
"ShippingCharges": 0.00,
"OtherCharges": 0.00,
"Rounding": 0.00,
"NetAmount": 8500.00,
"ITotlaMRP": 9600.00,
"TotalSavings": 2400.00,
"AcceptedBy": "John Doe",
"Remarks": null,
"SalesOrderDetailID": "Q2ZQdGhSZVhBaVlXTnpaWFF7TWk0d01B",
"ItemID": "Q2ZQdGhSZVhBaVlXTnpaWFF7T0M0d01B",
"ItemName": "Titan Rimless Frame",
"HSNCode": "90041000",
"ItemTypeName": "Frames",
"Qty": 1.00,
"MRP": 3200.00,
"Price": 2500.00,
"IDTotalMRP": 3200.00,
"IDTotalPrice": 2500.00,
"DiscountRate": 12.50,
"DiscountAmount": 400.00,
"TaxableAmount": 2100.00,
"SGSTRate": 9.00,
"SGSTAmount": 189.00,
"CGSTRate": 9.00,
"CGSTAmount": 189.00,
"IGSTRate": 0.00,
"IGSTAmount": 0.00,
"TotalAmount": 2478.00,
"Spherical_Right": "-2.00",
"Cylinder_Right": "-0.75",
"AXIS_Right": "175",
"ADD_Right": null,
"VA_Right": "6/9",
"VN_Right": null,
"PD_Right": "32",
"Spherical_Left": "-1.50",
"Cylinder_Left": "-0.50",
"AXIS_Left": "180",
"ADD_Left": null,
"VA_Left": "6/6",
"VN_Left": null,
"PD_Left": "32",
"Conditions": null,
"ShowPrescriptionOnSalesOrderPrint": 1,
"NameOfSalesOrderPrint": "Sales Order"
}
GET /api/Orders/{salesOrderNo}/ids
Bearer tokenResolves an order number into the encrypted IDs the rest of the API needs. This is the bridge between the number a human knows and the identifiers the API expects.
Path parameters
| Name | Type | Required | Description |
|---|---|---|---|
| salesOrderNo | string | Required | The order number. |
Response
{
"salesOrderID": "ENCRYPTED_SALES_ORDER_ID",
"customerID": "ENCRYPTED_CUSTOMER_ID"
}
Example request
curl "https://api.opto-soft.com/api/Orders/SO%2F2026%2F001842/ids" \
-H "Authorization: Bearer YOUR_TOKEN"
Example response — 200 OK
{
"SalesOrderID": "Q2ZQdGhSZVhBaVlXTnpaWFF3TWk0d01B",
"CustomerID": "Q2ZQdGhSZVhBaVlXTnpaWFF4TWk0d01B"
}
GET /api/Orders/{salesOrderId}/items
Bearer tokenReturns the line items on an order — frames, lenses, contact lenses and accessories, with quantity, price, discount and GST breakdown.
Path parameters
| Name | Type | Required | Description |
|---|---|---|---|
| salesOrderId | int64 | Required | Numeric sales order ID — not the encrypted string used elsewhere. |
This is the one order endpoint that takes a plain numeric ID. Passing the
encrypted id from
/api/Orders/search here will fail to bind.
Example request
curl https://api.opto-soft.com/api/Orders/104822/items \
-H "Authorization: Bearer YOUR_TOKEN"
Example response — 200 OK
[
{
"SalesOrderDetailID": "Q2ZQdGhSZVhBaVlXTnpaWFF3TWk0d01B",
"ItemID": "Q2ZQdGhSZVhBaVlXTnpaWFF4TWk0d01B",
"Name": "Titan Rimless Frame",
"Code": "TIT-001",
"Qty": 1.00,
"MRP": 3200.00,
"Price": 2500.00,
"DiscountAmount": 400.00,
"DiscountRate": 12.50,
"TaxableAmount": 2100.00,
"SGSTRate": 9.00,
"SGSTAmount": 189.00,
"CGSTRate": 9.00,
"CGSTAmount": 189.00,
"IGSTRate": 0.00,
"IGSTAmount": 0.00,
"TotalAmount": 2478.00
}
]
GET /api/Orders/{salesOrderId}/advance-receipt-data
Bearer tokenReturns everything needed to take an advance payment against an order: the customer, the order total, what has already been received and the balance still outstanding. Call this before recording a receipt.
Path parameters
| Name | Type | Required | Description |
|---|---|---|---|
| salesOrderId | string | Required | Encrypted sales order ID. |
Response
| Field | Type | Description |
|---|---|---|
| CustomerID | string | Encrypted customer ID. |
| CustomerName | string | Full customer name. |
| CustomerFirstName | string | Given name, for greetings on the receipt. |
| CustomerMobileNo | string | Mobile number. |
| CustmoerEmail | string | Email address. Note the spelling in the API. |
| Balance | number | Amount still outstanding. |
| ReceiptTotalAmount | number | Total already received. |
| SOTotalAmount | number | Order total. |
| SOTotalDiscountAmount | number | Discount on the order. |
| SalesOrderID | string | Encrypted order ID, echoed back. |
| StatusID | integer | Current order status. |
| MembershipID | string | Encrypted membership ID, if the customer is a member. |
Example request
curl https://api.opto-soft.com/api/Orders/ENCRYPTED_SALES_ORDER_ID/advance-receipt-data \
-H "Authorization: Bearer YOUR_TOKEN"
Errors
| Status | Cause |
|---|---|
| 404 | Order not found in the token’s scope. |
Example response — 200 OK
{
"CustomerID": "Q2ZQdGhSZVhBaVlXTnpaWFF3TWk0d01B",
"CustomerName": "Rajesh Sharma",
"CustomerFirstName": "Rajesh",
"CustomerMobileNo": "9876543210",
"CustmoerEmail": "rajesh.sharma@email.com",
"Balance": 5500.00,
"ReceiptTotalAmount": 3000.00,
"SOTotalAmount": 8500.00,
"SOTotalDiscountAmount": 1200.00,
"SalesOrderID": "Q2ZQdGhSZVhBaVlXTnpaWFF4TWk0d01B",
"StatusID": 2,
"MembershipID": "Q2ZQdGhSZVhBaVlXTnpaWFF5TWk0d01B"
}
Balances and dropdowns
GET /api/Orders/with-balance
Bearer tokenLists every order in a financial year that still has money outstanding. This is the receivables view — useful for a collections report or a payment-reminder campaign.
Query parameters
| Name | Type | Required | Description |
|---|---|---|---|
| yearId | int64 | Required | Financial year to report on. |
Example request
curl "https://api.opto-soft.com/api/Orders/with-balance?yearId=9" \
-H "Authorization: Bearer YOUR_TOKEN"
Example response — 200 OK
[
{
"BranchID": "Q2ZQdGhSZVhBaVlXTnpaWFF3TWk0d01B",
"SalesOrderIDInReceipt": "Q2ZQdGhSZVhBaVlXTnpaWFF4TWk0d01B",
"SONetAmount": 8500.00,
"ReceiptTotal": 3000.00,
"BalanceAmount": 5500.00,
"CustomerName": "Rajesh Sharma",
"MobileNumber": "9876543210",
"FullSalesOrderNo": "SO-2024-0568",
"ShortSalesOrderNo": "0568",
"CombinedSalesOrderNo": "SO-2024-0568 | Rajesh Sharma"
}
]
GET /api/Orders/dropdown
Bearer tokenA lightweight list of one customer’s orders with their balances — designed to populate a picker when taking a payment, so the cashier can see which order to apply it to.
Query parameters
| Name | Type | Required | Description |
|---|---|---|---|
| customerId | string | Required | Encrypted customer ID. |
| yearId | int64 | Required | Financial year. |
Response
| Field | Type | Description |
|---|---|---|
| SalesOrderID | string | Encrypted order ID. |
| SalesOrderNO | string | Order number. |
| SONetAmount | number | Order total. |
| ReceiptTotal | number | Received so far. |
| BalanceAmount | number | Outstanding balance. |
Example request
curl "https://api.opto-soft.com/api/Orders/dropdown?customerId=ENCRYPTED_CUSTOMER_ID&yearId=9" \
-H "Authorization: Bearer YOUR_TOKEN"
Example response — 200 OK
[
{
"SalesOrderID": "Q2ZQdGhSZVhBaVlXTnpaWFF3TWk0d01B",
"SalesOrderNO": "SO-2024-0568",
"SONetAmount": 8500.00,
"ReceiptTotal": 3000.00,
"BalanceAmount": 5500.00
}
]
Status and cancellation
GET /api/Orders/cancellation-reasons
Bearer tokenReturns the configured cancellation reasons. Fetch this once and cache it to populate a reason picker.
Response
| Field | Type | Description |
|---|---|---|
| ID | integer | Reason ID. |
| CancellationReason | string | Display text. |
| IsActive | boolean | Only offer active reasons. |
Example request
curl https://api.opto-soft.com/api/Orders/cancellation-reasons \
-H "Authorization: Bearer YOUR_TOKEN"
Example response — 200 OK
[
{
"ID": 1,
"CancellationReason": "Customer Request",
"IsActive": true
},
{
"ID": 2,
"CancellationReason": "Out of Stock",
"IsActive": true
},
{
"ID": 3,
"CancellationReason": "Duplicate Order",
"IsActive": true
}
]
PUT /api/Orders/{salesOrderId}/status
Bearer tokenMoves an order to a new status — for example from In Process to Ready for Delivery when the lab returns the job. This is the endpoint a lab-management or workflow integration drives.
Path parameters
| Name | Type | Required | Description |
|---|---|---|---|
| salesOrderId | string | Required | Encrypted sales order ID. |
Request body
| Field | Type | Required | Description |
|---|---|---|---|
| statusId | int64 | Required | Target status ID. Read current values from the statusID field on /api/Orders/search results. |
Example request
curl -X PUT https://api.opto-soft.com/api/Orders/ENCRYPTED_SALES_ORDER_ID/status \
-H "Authorization: Bearer YOUR_TOKEN" \
-H "Content-Type: application/json" \
-d '{ "statusId": 3 }'
Errors
| Status | Cause |
|---|---|
| 400 | Invalid status transition, or an unrecognised statusId. |
| 401 | Token missing or expired. |
| 500 | Server error. |
Example response — 200 OK
{
"success": true,
"message": "Sales order status updated successfully."
}
POST /api/Orders/{salesOrderId}/cancel
Bearer tokenCancels an order and simultaneously settles the treatment of any receipts already taken against it. Because it touches money, it takes both an order status and a receipt status.
Path parameters
| Name | Type | Required | Description |
|---|---|---|---|
| salesOrderId | string | Required | Encrypted sales order ID. |
Request body
| Field | Type | Required | Description |
|---|---|---|---|
| statusId | int32 | Required | Cancellation status to apply to the order. |
| receiptStatusId | int32 | Required | How to treat existing receipts (for example refunded or retained as credit). |
| date | date-time | Required | Cancellation date. |
| transactionDate | date-time | Required | Accounting date the cancellation posts against. |
Example request
curl -X POST https://api.opto-soft.com/api/Orders/ENCRYPTED_SALES_ORDER_ID/cancel \
-H "Authorization: Bearer YOUR_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"statusId": 5,
"receiptStatusId": 2,
"date": "2026-07-26T00:00:00",
"transactionDate": "2026-07-26T00:00:00"
}'
Cancellation is not reversible through the API. There is no un-cancel endpoint. Confirm the reason and the receipt treatment with the operator before calling this.
Errors
| Status | Cause |
|---|---|
| 400 | Order already cancelled or invoiced, or an invalid status combination. |
| 401 | Token missing or expired. |
| 500 | Server error. |
Example response — 200 OK
{
"success": true,
"message": "Sales order cancelled successfully."
}