Retrieve bills (checks/orders) with their aggregated amounts, including the payment and product-line records belonging to each bill.
| API name | order.bill |
| Version | v1 |
| Method | POST |
| Response format | JSON |
1. Getting started#
1.1 Credentials#
To call this API you need two values, issued by OrderPin:
| Credential | Sent as | Description |
|---|---|---|
| Client ID | X-Client-ID header |
Identifies your account |
| API key | Authorization: Bearer <api-key> header |
Secret key — keep it confidential |
Every request must include both. Requests are automatically scoped to the data of the store associated with your credentials; you cannot read another store's data, and you do not need to specify it.
Getting credentials: email api@orderpin.us with your Store ID — that is all OrderPin needs to issue your Client ID and API key. Credentials are issued per store: if your company has several stores, request a separate set for each Store ID.
1.2 Endpoint#
There are two equivalent ways to call the API.
Path form (recommended) — the API name is part of the URL:
POST https://{base-url}/orderpin/oapi/order/bill/v1
The version segment is optional — POST https://{base-url}/orderpin/oapi/order/bill uses the
current default version (v1). Pinning the version explicitly is recommended so that your
integration is not affected by future default-version changes.
⚠️ Because the API name
order.billcontains a dot, the path form splits it into URL segments (/order/bill). Take care not to confusePOST …/order/billwith the Transaction API'sPOST …/order/bill/transaction.
Generic form — call the base endpoint and declare the API in the request body:
POST https://{base-url}/orderpin/oapi
{
"subject": "order.bill",
"version": "v1",
"filter": { "createStartAt": 1767225600000, "createEndAt": 1769817600000 }
}
With this form, subject is required (an unknown or missing subject/version is reported with
code 1020); version is optional and defaults to v1. All other parameters (§3) are the same.
The path form is preferred.
1.3 Request headers#
| Header | Required | Value |
|---|---|---|
X-Client-ID |
Yes | Your client ID |
Authorization |
Yes | Bearer <your-api-key> |
Content-Type |
Yes | application/json |
A JSON request body is always required, even when you have no filters to send — use
{}. A request with an empty body is rejected.
2. Request format#
{
"filter": {
"<parameter>": "<value or array of values>"
},
"skip": 0,
"limit": 100,
"sort": [
{ "field": "<field name>", "type": "desc" }
],
"with_record_count": true
}
Example#
POST /orderpin/oapi/order/bill/v1 HTTP/1.1
Host: {base-url}
X-Client-ID: US00000001
Authorization: Bearer sk_live_xxxxxxxxxxxxxxxxxxxx
Content-Type: application/json
{
"filter": {
"billType": ["sales", "refund"],
"createStartAt": 1767225600000,
"createEndAt": 1769817600000
},
"skip": 0,
"limit": 100,
"sort": [{ "field": "createAt", "type": "desc" }],
"with_record_count": true
}
3. Request parameters#
Unrecognized parameters are rejected. Only the parameters listed below are accepted.
3.1 Query parameters#
| Parameter | Type | Required | Default | Description |
|---|---|---|---|---|
subject |
string | No | — | Only needed if you call the generic endpoint POST /orderpin/oapi; set it to order.bill. Not required when using the endpoint in §1.2. |
version |
string | No | v1 |
Only used together with subject on the generic endpoint. |
filter |
object | Yes | — | One or more filter parameters (§3.2). At least one filter is required; a request with an empty filter object is rejected. Multiple filters are combined with logical AND. |
skip |
integer | No | 0 |
Number of records to skip. Use together with limit to page through results. |
limit |
integer | No | 200 |
Maximum number of records to return, capped at 200. Values above 200 or below 1 are treated as 200. |
sort |
object or array of objects | No | none | Sort order. Each item is { "field": "<name>", "type": "asc" \| "desc" } — both keys are required. See §5 for sortable fields. |
with_record_count |
boolean | No | false |
When true, the response includes recordCount: the total number of records matching your filter, ignoring skip and limit. |
3.2 Filter parameters#
| Parameter | Type | Accepted form | Description |
|---|---|---|---|
uid |
string | single value or array | Match bills by their uid. Empty strings are rejected. |
billType |
string enum | single value or array | Match by bill type. Accepted values: sales, refund, giftcard, giftcardRefund. Any other value is rejected. |
createAt |
integer (epoch ms) | single value or array | Match bills checked out at exactly this timestamp. Must be greater than 0 and at least 15 minutes in the past (see Data freshness below). |
createStartAt |
integer (epoch ms) | single value | Start of a checkout-time range (inclusive). Must be sent together with createEndAt. |
createEndAt |
integer (epoch ms) | single value | End of a checkout-time range (exclusive). Must be sent together with createStartAt. |
modifiedAt |
integer (epoch ms) | single value or array | Match bills last modified at exactly this timestamp. Must be greater than 0 and at least 15 minutes in the past. |
modifiedStartAt |
integer (epoch ms) | single value | Start of a last-modified range (inclusive). Must be sent together with modifiedEndAt. |
modifiedEndAt |
integer (epoch ms) | single value | End of a last-modified range (exclusive). Must be sent together with modifiedStartAt. |
All timestamps are Unix epoch time in milliseconds (UTC).
Note:
createAtreflects the bill's checkout time, not the moment the record was written.
Data freshness#
Reporting data becomes queryable approximately 15 minutes after it is recorded. A timestamp
filter value that is less than 15 minutes (900000 ms) old — or in the future — is rejected with
data from the last 15 minutes is not yet available for query. When polling for new data, end your
query range at now - 15 minutes.
Using date ranges#
createStartAt / createEndAt and modifiedStartAt / modifiedEndAt are paired range filters:
- Both halves must be sent together. Sending only one is rejected with
missing pair filter. - The range is half-open:
start <= value < end. startmust be earlier thanend.- The span must be less than 31 days (
2678400000ms). Wider ranges are rejected. - Each half must also satisfy the data freshness rule above.
This makes range filters ideal for incremental synchronization: poll with
modifiedStartAt / modifiedEndAt to pick up records that changed since your last run.
Value types#
Values are type-checked. Sending a number where a string is expected (or vice versa) is rejected —
for example, uid must be a string, and modifiedAt must be a number.
When a filter accepts an array, it behaves as "matches any of these values".
4. Response format#
A successful request returns HTTP 200 with a JSON object. One record per bill — amounts are
already aggregated across the bill's transactions:
{
"bill": [
{
"uid": "B202601010001",
"billNo": "N-0001",
"originalBillId": "",
"originalBillNo": "",
"createAt": 1767254400000,
"modifiedAt": 1767254400000,
"table": "A1",
"mealNo": "M0001",
"employeeId": "E10086",
"employeeName": "Alice",
"shiftId": "S5001",
"orderType": 10,
"billType": "sales",
"itemsAmount": 5000,
"autoPricing": 0,
"temporaryCharge": 0,
"grossAmount": 5000,
"discount": 500,
"netAmount": 4500,
"taxes": 405,
"surcharge": 0,
"serviceCharge": 0,
"tips": 900,
"grossReceipts": 5805,
"payments": [
{
"uid": "T900001",
"status": "settled",
"paymentMethod": "card",
"paymentChannel": "STRIPE",
"EDCType": "credit",
"cardIssuer": "VISA",
"cardNo": "4242XXXXXXXX4242",
"authCode": "A12345",
"currency": "USD",
"amount": 5805,
"tipsAmount": 900,
"commissionFee": 0,
"ccCost": 170,
"deviceId": "D001",
"deviceName": "Terminal 1",
"createAt": 1767254400000,
"modifiedAt": 1767254400000
}
],
"products": [
{
"uid": "I700001",
"skuId": "SKU001",
"skuName": "Burger",
"quantity": 2,
"currency": "USD",
"productDiscount": 0,
"discount": 500,
"coupon": 0,
"grossSales": 5000,
"amount": 4500,
"taxes": 450,
"taxesExempt": 0,
"taxesPay": 405,
"category": [
{ "categoryId": "C01", "categoryName": "Mains" }
]
}
]
}
],
"recordCount": 300
}
| Key | Type | Description |
|---|---|---|
bill |
array | The matching bills. May be empty; treat a missing key as an empty list. |
recordCount |
integer | Total matching records. Only present when with_record_count is true. |
All monetary values are integers in the currency's minor units (e.g. cents — 5805 = 58.05).
4.1 Bill fields (bill[])#
| Field | Type | Description |
|---|---|---|
uid |
string | Unique identifier of the bill. Use this value with the uid filter, and with the Transaction API's billId filter. |
billNo |
string | Human-readable bill number. |
originalBillId |
string | uid of the bill this one refers to (refunds/voids). Empty for ordinary bills. |
originalBillNo |
string | Bill number of originalBillId. |
createAt |
integer (epoch ms) | Checkout time. |
modifiedAt |
integer (epoch ms) | Time the bill was last modified. |
table |
string | Table number. May be empty (e.g. takeout). |
mealNo |
string | Meal/course number. May be empty. |
employeeId |
string | Identifier of the employee who operated the bill. |
employeeName |
string | Employee display name. May be empty. |
shiftId |
string | Identifier of the shift the bill belongs to. |
orderType |
integer | Order sub-type code (raw value). |
billType |
string | sales, refund, giftcard or giftcardRefund. |
itemsAmount |
integer | Net item subtotal, excluding tax. |
autoPricing |
integer | Automatic pricing adjustments (e.g. happy-hour). |
temporaryCharge |
integer | Temporary charges added to the bill. |
grossAmount |
integer | itemsAmount + autoPricing + temporaryCharge. |
discount |
integer | Bill-level discount total. |
netAmount |
integer | grossAmount - discount. |
taxes |
integer | Tax payable. |
surcharge |
integer | Credit-card surcharge. |
serviceCharge |
integer | Service fee. |
tips |
integer | Total tips (voided tips excluded). 0 when no tips were given. |
grossReceipts |
integer | netAmount + taxes + surcharge + serviceCharge + tips. |
payments |
array | Payment transactions for this bill — see §4.2. Empty when unpaid. |
products |
array | Product lines for this bill — see §4.3. Empty for gift-card bills. |
4.2 Payment fields (bill[].payments[])#
| Field | Type | Description |
|---|---|---|
uid |
string | Unique identifier of the transaction. |
status |
string | auth, sale, captured, change, voided, refunded or settled. |
paymentMethod |
string | cash, card, gift_card, balance or others. |
paymentChannel |
string | Payment channel identifier. May be empty. |
EDCType |
string | Card type — only for paymentMethod: "card": credit/debit, credit, debit, ebt-cash, ebt-food, e-wallet or others. Empty otherwise. |
cardIssuer |
string | Card brand/issuer. May be empty. |
cardNo |
string | Masked card number. May be empty. |
authCode |
string | Authorization code. May be empty. |
currency |
string | Currency code. |
amount |
integer | Transaction amount. |
tipsAmount |
integer | Tip included in this transaction. |
commissionFee |
integer | Surcharge/commission on this transaction. |
ccCost |
integer | Credit-card processing cost. |
deviceId |
string | Payment device identifier. |
deviceName |
string | Payment device name. |
createAt |
integer (epoch ms) | Transaction time. |
modifiedAt |
integer (epoch ms) | Time the transaction was last modified. |
The same records can be queried flat (across bills) via the Transaction API.
4.3 Product fields (bill[].products[])#
| Field | Type | Description |
|---|---|---|
uid |
string | Unique identifier of the line item. |
skuId |
string | SKU identifier. |
skuName |
string | Item name. |
quantity |
number | Quantity, rounded to 2 decimals (supports fractional amounts, e.g. weight-based items). |
currency |
string | Currency code. |
productDiscount |
integer | Automatic item-level discount. |
discount |
integer | Manual discount on this line. |
coupon |
integer | Coupon amount applied to this line. |
grossSales |
integer | Gross sales for the line, excluding tax. |
amount |
integer | Net sales: grossSales - (productDiscount + discount + coupon). |
taxes |
integer | Total tax for the line. |
taxesExempt |
integer | Tax-exempt portion. |
taxesPay |
integer | Tax payable. |
category |
array | Category records for the item (categoryId, categoryName). May be empty. |
5. Sorting and pagination#
5.1 Sorting#
sort accepts a single object or an array of objects (applied in order):
"sort": [
{ "field": "createAt", "type": "desc" },
{ "field": "uid", "type": "asc" }
]
type must be asc or desc, and both field and type are required.
Sortable fields: uid, billNo, createAt, modifiedAt.
Sorting by
billNoorders by checkout time, not alphabetically by the number itself.
Sorting is supported only on these fields. Using any other name is rejected:
- A valid response field that does not support sorting (e.g.
billType,netAmount) →field <name> not sortable - A name that is not a response field (including filter names such as
modifiedStartAt) →unknown sort field <name>
5.2 Pagination#
Results are limited to 200 records per request. To page through a larger result set:
- Send the first request with
"with_record_count": true,"skip": 0,"limit": 200. - Read
recordCountfrom the response to determine the number of pages. - Repeat, increasing
skipbylimiteach time, until all records are retrieved.
Always use a sort when paginating so that the ordering is stable across pages.
6. Errors#
Successful requests return HTTP 200. Errors return the HTTP status that matches the error
category, with a JSON body:
{
"code": 1004,
"msg": "Error: [order.bill.v1] filter billType: value expected [sales | refund | giftcard | giftcardRefund] [bogus]"
}
Read the code field for the precise reason. The HTTP status groups errors by category: 4xx for
request and credential problems, 429 for rate limiting, 5xx for server-side failures.
| HTTP | Code | Meaning | What to do |
|---|---|---|---|
401 |
2004 |
Missing X-Client-ID header |
Add the header to every request. |
401 |
2003 |
Missing or malformed Authorization header |
Use the exact form Bearer <api-key>. |
401 |
2001 |
Unknown client ID | Verify the client ID issued to you. |
401 |
2005 |
Invalid API key | Verify the key; email api@orderpin.us to have it reissued if lost. |
403 |
2006 |
Open API is not enabled for your account | Email api@orderpin.us with your Store ID to have the API enabled. |
400 |
1004 |
Invalid request parameter | The msg field names the offending parameter and reason — see §6.1. |
200 |
1020 |
Unknown API name or version | Check the endpoint path, subject and version. Note this case still returns HTTP 200, so always check the body code. |
429 |
2012 |
Rate limit exceeded | Honor the Retry-After header — see §7. |
500 |
1003 |
Data retrieval failed | Transient — retry with backoff. |
500 |
1012 |
Internal error | Transient — retry with backoff; email api@orderpin.us if it persists. |
6.1 Common 1004 validation messages#
| Message | Cause |
|---|---|
unknown request param: <name> |
A parameter outside §3.1 was sent. |
no filter |
filter is empty or missing. At least one filter is required. |
unknown filter key: <name> |
A filter outside §3.2 was sent. |
filter uid: uid is empty [""] |
An empty string was sent for uid. |
filter billType: value expected [sales \| refund \| giftcard \| giftcardRefund] |
An invalid billType value was sent. |
filter <name>: less than 0 [<value>] |
A timestamp filter is <= 0. |
filter <name>: data from the last 15 minutes is not yet available for query [<value>] |
A timestamp filter is less than 15 minutes old — see Data freshness (§3.2). |
missing pair filter [<name>] |
A range filter was sent without its counterpart. |
END value less than START |
The range end is not after the range start. |
between range greater than 2678400000 |
The range spans 31 days or more. |
expect string but got: <value> |
A filter value of the wrong type (e.g. a number sent for uid). |
parse sort field error: field <name> not sortable |
sort named a response field that does not support sorting (§5.1). |
parse sort field error: unknown sort field <name> |
sort named something that is not a response field. |
no sort field / sort type expect [asc \| desc] |
A sort item is missing field, or type is not asc/desc. |
7. Rate limits#
Requests are limited per client ID, and the quota is shared across the whole service — running several workers in parallel does not give you extra capacity.
The exact values are configured per environment. Read them from the response headers rather than hard-coding them:
| Header | Returned on | Meaning |
|---|---|---|
X-RateLimit-Limit |
every response | Your quota, i.e. the maximum number of requests per window. |
X-RateLimit-Remaining |
every response | Quota left in the current window. Best-effort — it can lag slightly under concurrent load. |
Retry-After |
429 only |
Whole seconds to wait before retrying. |
When you exceed the quota the response is:
HTTP/1.1 429 Too Many Requests
Retry-After: 37
X-RateLimit-Limit: 60
X-RateLimit-Remaining: 0
{ "code": 2012, "msg": "[2012] Error: rate limit exceeded, retry after 37 second(s)" }
The window slides continuously rather than resetting at a fixed boundary, so quota comes back
gradually. Waiting the full Retry-After is always sufficient.
How to stay within the limit#
- Retry on
429usingRetry-After, not a fixed sleep. Add jitter if you run multiple workers. - Watch
X-RateLimit-Remainingand slow down before you hit zero. - Prefer wide queries over many narrow ones. Bill records are large (they embed payments and
products); a range query with
limit: 200is far cheaper than one request per bill. - Do not retry
5xxin a tight loop — those attempts also consume quota.
Rate limiting is applied only after your credentials have been verified, so a request that fails authentication does not consume your quota.
8. Recommendations#
- Filter by
modifiedStartAt/modifiedEndAtfor incremental synchronization; bills change after checkout (tips, refunds, voids), and the modified range picks all of that up. - Keep ranges under 31 days. Split longer periods into multiple requests.
- Always send a
sortwhen paginating so pages do not overlap or skip records. - Request
with_record_countonce (on the first page) rather than on every page. - Handle refunds explicitly.
billType: "refund"bills reference the original viaoriginalBillId; include or exclude them deliberately in revenue calculations. - Exclude
voidedpayments from tender totals. - Protect your API key. Send it only over HTTPS, never embed it in client-side code, and email api@orderpin.us immediately if you suspect it has been exposed.
- Retry transient failures (
1003,1012) with exponential backoff, and honorRetry-Afteron429.
9. Full example#
curl -X POST 'https://{base-url}/orderpin/oapi/order/bill/v1' \
-H 'X-Client-ID: US00000001' \
-H 'Authorization: Bearer sk_live_xxxxxxxxxxxxxxxxxxxx' \
-H 'Content-Type: application/json' \
-d '{
"filter": {
"createStartAt": 1767225600000,
"createEndAt": 1769817600000
},
"skip": 0,
"limit": 200,
"sort": [{ "field": "createAt", "type": "desc" }],
"with_record_count": true
}'