OrderPin Open API
Open API/Orders/Bill transaction

Transaction API order.bill.transaction

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: createAt reflects 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.
  • start must be earlier than end.
  • The span must be less than 31 days (2678400000 ms). 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 billNo orders 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:

  1. Send the first request with "with_record_count": true, "skip": 0, "limit": 200.
  2. Read recordCount from the response to determine the number of pages.
  3. Repeat, increasing skip by limit each 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 429 using Retry-After, not a fixed sleep. Add jitter if you run multiple workers.
  • Watch X-RateLimit-Remaining and slow down before you hit zero.
  • Prefer wide queries over many narrow ones. A single request with a date range and limit: 200 costs one unit of quota and returns up to 200 records.
  • Do not retry 5xx in 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 / modifiedEndAt for incremental synchronization — post-payment tip adjustments appear as modifications.
  • Keep ranges under 31 days. Split longer periods into multiple requests.
  • Always send a sort when paginating so pages do not overlap or skip records.
  • Request with_record_count once (on the first page) rather than on every page.
  • Exclude voided transactions from tender totals, and treat refunded ones according to your accounting rules.
  • Reconcile per bill with the billId filter, 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 honor Retry-After on 429.

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