Retrieve payment transactions (tenders) across bills — card, cash, gift-card and balance payments, with card and device details.
| API name | order.bill.transaction |
| 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/transaction/v1
The version segment is optional — POST https://{base-url}/orderpin/oapi/order/bill/transaction
uses the current default version (v1). Pinning the version explicitly is recommended so that your
integration is not affected by future default-version changes.
Generic form — call the base endpoint and declare the API in the request body:
POST https://{base-url}/orderpin/oapi
{
"subject": "order.bill.transaction",
"version": "v1",
"filter": { "paymentMethod": "card", "modifiedStartAt": 1767225600000, "modifiedEndAt": 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/transaction/v1 HTTP/1.1
Host: {base-url}
X-Client-ID: US00000001
Authorization: Bearer sk_live_xxxxxxxxxxxxxxxxxxxx
Content-Type: application/json
{
"filter": {
"paymentMethod": "card",
"modifiedStartAt": 1767225600000,
"modifiedEndAt": 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.transaction. 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 transactions by their uid (the transaction identifier). Empty strings are rejected. |
billId |
string | single value or array | Match transactions belonging to the given bill(s). Empty strings are rejected. |
status |
string enum | single value or array | Match by transaction status. Accepted values: auth, sale, captured, change, voided, settled, refunded. Any other value is rejected. |
paymentMethod |
string enum | single value or array | Match by payment method. Accepted values: cash, card, gift_card, balance, others. Any other value is rejected. |
createAt |
integer (epoch ms) | single value or array | Match transactions made 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 transaction-time range (inclusive). Must be sent together with createEndAt. |
createEndAt |
integer (epoch ms) | single value | End of a transaction-time range (exclusive). Must be sent together with createStartAt. |
modifiedAt |
integer (epoch ms) | single value or array | Match transactions 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 transaction (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 — tips
added after payment, for instance, show up as a modification.
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:
{
"payments": [
{
"uid": "T900001",
"billId": "B202601010001",
"billNo": "N-0001",
"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,
"employeeId": "E10086"
}
],
"recordCount": 87
}
| Key | Type | Description |
|---|---|---|
payments |
array | The matching transactions. 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 Transaction fields (payments[])#
| Field | Type | Description |
|---|---|---|
uid |
string | Unique identifier of the transaction. Use this value with the uid filter. |
billId |
string | Identifier of the bill this transaction belongs to. Matches uid in the Bill API; use it with the billId filter. |
billNo |
string | Human-readable bill number. |
status |
string | Transaction status — see the table below. |
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. |
employeeId |
string | Identifier of the employee who processed the transaction. |
status values#
| Value | Meaning |
|---|---|
auth |
Card authorized; not yet captured. |
sale |
Direct sale (auth + capture in one step). |
captured |
A previously authorized amount was captured. |
change |
Cash change given. |
voided |
The transaction was canceled — exclude it from tender totals. |
refunded |
The amount was refunded. |
settled |
The transaction is settled/finalized. |
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": "billId", "type": "asc" }
]
type must be asc or desc, and both field and type are required.
Sortable fields: billId, billNo, createAt, modifiedAt.
Sorting by
billNoorders by transaction 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.
uid,amount,paymentMethod) →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.transaction.v1] filter status: value expected [auth | sale | captured | change | voided | settled | refunded] [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 billId: billId is empty [""] |
An empty string was sent for billId. |
filter status: value expected [auth \| sale \| captured \| change \| voided \| settled \| refunded] |
An invalid status value was sent. |
filter paymentMethod: value expected [cash \| card \| gift_card \| balance \| others] |
An invalid paymentMethod 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. A single request with a date range and
limit: 200costs one unit of quota and returns up to 200 records. - 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 — post-payment tip adjustments appear as modifications. - 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. - Exclude
voidedtransactions from tender totals, and treatrefundedones according to your accounting rules. - Reconcile per bill with the
billIdfilter, or use the Bill API when you need bills with their payments embedded. - 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/transaction/v1' \
-H 'X-Client-ID: US00000001' \
-H 'Authorization: Bearer sk_live_xxxxxxxxxxxxxxxxxxxx' \
-H 'Content-Type: application/json' \
-d '{
"filter": {
"modifiedStartAt": 1767225600000,
"modifiedEndAt": 1769817600000
},
"skip": 0,
"limit": 200,
"sort": [{ "field": "createAt", "type": "desc" }],
"with_record_count": true
}'