Skip to main content

Transaction Batching

Overview

A TransactionBatch groups PaymentTransactions together for settlement and payout. An owner has one open batch per batch bucket, which for most owners means a single open batch. New transactions accumulate into it until it closes. Closing a batch on demand opens a replacement immediately; a batch that closes on its own schedule gets no replacement, and the next transaction to arrive opens one.

A batch's owner is either a Merchant or a Payfac (TransactionBatchOwner). A sub-merchant is itself a Merchant — reachable from its payfac via Payfac.submerchants — so this union already covers merchant, sub-merchant, and payfac batch owners.

Batch statuses

A batch starts OPEN and accumulates transactions. Closing it (see Close a batch for payout) moves it to CLOSE_REQUESTED, and it proceeds through settlement, clearing, and disbursement toward PAYOUT_COMPLETE. Batches can also land in holding or terminal states.

The TransactionBatchStatus values:

StatusMeaning
OPENThe batch is currently open and accepting transactions.
CLOSE_REQUESTEDA close has been requested for the batch.
PROCESSINGThe batch is processing.
ON_HOLDThe batch is on hold.
CLOSEDThe batch is closed.
CLEAR_PENDINGThe batch is pending clearing.
CLEAREDThe batch has cleared.
DISBURSEMENT_PENDINGThe batch is pending disbursement.
PAYOUT_COMPLETEThe batch payout is complete.
REJECTEDThe batch was rejected.
DISBURSEDThe batch was disbursed.
PAYOUT_PENDINGThe batch payout is pending.
PAYOUT_FAILEDThe batch payout failed.

How a batch accumulates

Highnote adds a transaction's amount and any associated fees to its batch's running totals as the transaction moves through the payment lifecycle — for example, as a purchase is captured and cleared, as funds are disbursed, or if a first chargeback is filed against a transaction in the batch. A batch's debitTotal and creditTotal total the transactions that debit or credit the customer. chargebackTotal is a breakdown rather than a separate bucket — chargebacks are also counted in debitTotal or creditTotal by direction, so summing all three double-counts them. currentTotal is the signed running total of all transactions before fees, holds, and adjustments; it is null for batches created before the field was introduced. disbursedAmount is what the owner is actually paid, and it is currentTotal less fees, holds, and adjustments — so it will not reconcile against currentTotal alone.

List transaction batches

Use the transactionBatches query to list an owner's batches — the open one, historical ones, or a filtered slice by status or date. Results are scoped to your API key's organization context. If you're a payfac, a query without further scoping returns your own batch plus one for each of your sub-merchants — scope to a single owner with ownerId (see the Highnote Query Language (HQL) fields below), or select owner to tell them apart. If you build that filter from a value supplied by one of your sub-merchants, validate its format before interpolating it — see Build query strings safely.

transactionBatches(
first: Int = 20
after: String
filterBy: TransactionBatchFilterInput
): TransactionBatchConnectionPayload

TransactionBatchConnectionPayload is a union of AccessDeniedError, UserError, and TransactionBatchConnection — select on the concrete type with ... on. TransactionBatchFilterInput.searchQueryLanguage accepts an HQL query string and cannot be combined with other filters.

The open batch

Most owners have a single open batch, so filtering on status = 'OPEN' returns it (plus one per sub-merchant, for a payfac). An owner routed across several batch buckets has one open batch per bucket, and an owner whose last batch closed on schedule has none until its next transaction arrives:

List the open batch
Query
query QueryOpenTransactionBatch($filterBy: TransactionBatchFilterInput!) {
transactionBatches(filterBy: $filterBy) {
__typename
... on AccessDeniedError {
message
}
... on UserError {
errors {
errorPath
code
description
}
}
... on TransactionBatchConnection {
pageInfo {
hasNextPage
hasPreviousPage
startCursor
endCursor
}
edges {
cursor
node {
id
owner {
__typename
... on Merchant {
id
}
... on Payfac {
id
}
}
status
debitTotal {
count
totalAmount {
value
currencyCode
decimalPlaces
}
}
creditTotal {
count
totalAmount {
value
currencyCode
decimalPlaces
}
}
chargebackTotal {
count
totalAmount {
value
currencyCode
decimalPlaces
}
}
holdsTotalAmount {
value
currencyCode
decimalPlaces
}
adjustmentsTotalAmount {
value
currencyCode
decimalPlaces
}
}
}
}
}
}
Variables
{
"filterBy": {
"searchQueryLanguage": {
"query": "status = 'OPEN'",
"version": "VERSION_1"
}
}
}
⚠️ Please login to execute queries. Visit the dashboard to authenticate.
Result
{
"data": {
"transactionBatches": {
"__typename": "TransactionBatchConnection",
"pageInfo": {
"hasNextPage": false,
"hasPreviousPage": false,
"startCursor": "<CURSOR_TRANSACTION_BATCH_OPEN>",
"endCursor": "<CURSOR_TRANSACTION_BATCH_PAYFAC_OPEN>"
},
"edges": [
{
"cursor": "<CURSOR_TRANSACTION_BATCH_OPEN>",
"node": {
"id": "<TRANSACTION_BATCH_ID_OPEN>",
"owner": {
"__typename": "Merchant",
"id": "<MERCHANT_ID>"
},
"status": "OPEN",
"debitTotal": {
"count": 2,
"totalAmount": {
"value": 10000,
"currencyCode": "USD",
"decimalPlaces": 2
}
},
"creditTotal": {
"count": 1,
"totalAmount": {
"value": 2500,
"currencyCode": "USD",
"decimalPlaces": 2
}
},
"chargebackTotal": {
"count": 0,
"totalAmount": {
"value": 0,
"currencyCode": "USD",
"decimalPlaces": 2
}
},
"holdsTotalAmount": {
"value": -100,
"currencyCode": "USD",
"decimalPlaces": 2
},
"adjustmentsTotalAmount": {
"value": -50,
"currencyCode": "USD",
"decimalPlaces": 2
}
}
},
{
"cursor": "<CURSOR_TRANSACTION_BATCH_PAYFAC_OPEN>",
"node": {
"id": "<TRANSACTION_BATCH_ID_PAYFAC_OPEN>",
"owner": {
"__typename": "Payfac",
"id": "<PAYFAC_ID>"
},
"status": "OPEN",
"debitTotal": {
"count": 1,
"totalAmount": {
"value": 4000,
"currencyCode": "USD",
"decimalPlaces": 2
}
},
"creditTotal": {
"count": 0,
"totalAmount": {
"value": 0,
"currencyCode": "USD",
"decimalPlaces": 2
}
},
"chargebackTotal": {
"count": 0,
"totalAmount": {
"value": 0,
"currencyCode": "USD",
"decimalPlaces": 2
}
},
"holdsTotalAmount": {
"value": 0,
"currencyCode": "USD",
"decimalPlaces": 2
},
"adjustmentsTotalAmount": {
"value": 0,
"currencyCode": "USD",
"decimalPlaces": 2
}
}
}
]
}
},
"extensions": {
"requestId": "<REQUEST_ID>"
}
}

TransactionBatchFilterInput supports five HQL fields:

FieldTypeOperatorsExample
idID=, !=id = 'acqtb_open_1'
ownerIdID=, !=ownerId = 'acqmh_merchant_1'
statusTransactionBatchStatus=, !=status = 'OPEN'
currentTotalAmountCurrency amount=, !=, >=, <=, >, <currentTotalAmount >= 'USD 300'
createdAtISO 8601 timestamp=, !=, >=, <=, >, <createdAt >= '2025-12-09T00:00:00Z'
Money values are in minor units

An HQL money literal carries the amount in the currency's minor units, so currentTotalAmount >= 'USD 300' matches batches at or above $3.00, not $300. The comparison also scopes results to the currency you name.

So in addition to status = OPEN, you can filter to a specific status such as status = 'ON_HOLD', exclude one with status != 'OPEN' (below), scope to a single owner with ownerId, or filter by creation date or running total.

Non-open batches

Filter on status != OPEN to list an owner's historical and in-flight batches:

List non-open batches
Query
query QueryNonOpenTransactionBatch($filterBy: TransactionBatchFilterInput!) {
transactionBatches(filterBy: $filterBy) {
__typename
... on AccessDeniedError {
message
}
... on UserError {
errors {
errorPath
code
description
}
}
... on TransactionBatchConnection {
pageInfo {
hasNextPage
hasPreviousPage
startCursor
endCursor
}
edges {
cursor
node {
id
status
}
}
}
}
}
Variables
{
"filterBy": {
"searchQueryLanguage": {
"query": "status != 'OPEN'",
"version": "VERSION_1"
}
}
}
⚠️ Please login to execute queries. Visit the dashboard to authenticate.
Result
{
"data": {
"transactionBatches": {
"__typename": "TransactionBatchConnection",
"pageInfo": {
"hasNextPage": false,
"hasPreviousPage": false,
"startCursor": "<CURSOR_TRANSACTION_BATCH_PROCESSING>",
"endCursor": "<CURSOR_TRANSACTION_BATCH_ON_HOLD>"
},
"edges": [
{
"cursor": "<CURSOR_TRANSACTION_BATCH_PROCESSING>",
"node": {
"id": "<TRANSACTION_BATCH_ID_PROCESSING>",
"status": "PROCESSING"
}
},
{
"cursor": "<CURSOR_TRANSACTION_BATCH_CLOSED>",
"node": {
"id": "<TRANSACTION_BATCH_ID_CLOSED>",
"status": "CLOSED"
}
},
{
"cursor": "<CURSOR_TRANSACTION_BATCH_ON_HOLD>",
"node": {
"id": "<TRANSACTION_BATCH_ID_ON_HOLD>",
"status": "ON_HOLD"
}
}
]
}
},
"extensions": {
"requestId": "<REQUEST_ID>"
}
}

Inspect a batch

A TransactionBatch implements Node, so once you have a batch's id — from a list result above, or from another reference — fetch it directly through the top-level node query. The example below inspects a closed batch, because fees and disbursedAmount are produced by the close workflow and are null while a batch is still open:

Inspect a batch
Query
query QueryTransactionBatchNode($id: ID!) {
node(id: $id) {
__typename
... on TransactionBatch {
id
owner {
__typename
... on Merchant {
id
}
... on Payfac {
id
}
}
status
createdAt
closeRequestedAt
currentTotal {
count
totalAmount {
value
currencyCode
decimalPlaces
}
}
debitTotal {
count
totalAmount {
value
currencyCode
decimalPlaces
}
}
creditTotal {
count
totalAmount {
value
currencyCode
decimalPlaces
}
}
chargebackTotal {
count
totalAmount {
value
currencyCode
decimalPlaces
}
}
holdsTotalAmount {
value
currencyCode
decimalPlaces
}
adjustmentsTotalAmount {
value
currencyCode
decimalPlaces
}
disbursedAmount {
value
currencyCode
decimalPlaces
}
fees {
__typename
accountingDirection
description
feeAmount {
value
currencyCode
decimalPlaces
}
}
}
}
}
Variables
{
"id": "<TRANSACTION_BATCH_ID_CLOSED>"
}
⚠️ Please login to execute queries. Visit the dashboard to authenticate.
Result
{
"data": {
"node": {
"__typename": "TransactionBatch",
"id": "<TRANSACTION_BATCH_ID_CLOSED>",
"owner": {
"__typename": "Merchant",
"id": "<MERCHANT_ID>"
},
"status": "CLOSED",
"createdAt": "2026-07-01T00:00:00.000Z",
"closeRequestedAt": "2026-07-08T00:00:00.000Z",
"currentTotal": {
"count": 3,
"totalAmount": {
"value": 7500,
"currencyCode": "USD",
"decimalPlaces": 2
}
},
"debitTotal": {
"count": 2,
"totalAmount": {
"value": 10000,
"currencyCode": "USD",
"decimalPlaces": 2
}
},
"creditTotal": {
"count": 1,
"totalAmount": {
"value": 2500,
"currencyCode": "USD",
"decimalPlaces": 2
}
},
"chargebackTotal": {
"count": 0,
"totalAmount": {
"value": 0,
"currencyCode": "USD",
"decimalPlaces": 2
}
},
"holdsTotalAmount": {
"value": -100,
"currencyCode": "USD",
"decimalPlaces": 2
},
"adjustmentsTotalAmount": {
"value": -50,
"currencyCode": "USD",
"decimalPlaces": 2
},
"disbursedAmount": {
"value": 7200,
"currencyCode": "USD",
"decimalPlaces": 2
},
"fees": [
{
"__typename": "TransactionBatchProcessingFee",
"accountingDirection": "DEBIT",
"description": "Processing fee",
"feeAmount": {
"value": 150,
"currencyCode": "USD",
"decimalPlaces": 2
}
}
]
}
},
"extensions": {
"requestId": "<REQUEST_ID>"
}
}

Drill into a batch's transactions

The transactions field on TransactionBatch returns the PaymentTransactions inside a batch, letting you break it down by type or by amount. Results are always scoped to the batch you're querying — the batchId filter is applied automatically, so you never supply it yourself. Alias the field (purchases: / refunds: below) to pull multiple filtered slices in one request.

Drill into a batch's transactions
Query
query QueryTransactionBatchByTransactionType($id: ID!) {
node(id: $id) {
__typename
... on TransactionBatch {
id
status
purchases: transactions(
filterBy: {
searchQueryLanguage: {
query: "accountingDirection = 'DEBIT'"
version: VERSION_1
}
}
) {
__typename
... on AccessDeniedError {
message
}
... on UserError {
errors {
errorPath
code
description
}
}
... on PaymentTransactionsConnection {
pageInfo {
hasNextPage
hasPreviousPage
startCursor
endCursor
}
edges {
cursor
node {
__typename
... on PaymentDebitTransaction {
id
capturedAmount {
value
currencyCode
decimalPlaces
}
}
}
}
}
}
refunds: transactions(
filterBy: {
searchQueryLanguage: {
query: "accountingDirection = 'CREDIT'"
version: VERSION_1
}
}
) {
__typename
... on AccessDeniedError {
message
}
... on UserError {
errors {
errorPath
code
description
}
}
... on PaymentTransactionsConnection {
pageInfo {
hasNextPage
hasPreviousPage
startCursor
endCursor
}
edges {
cursor
node {
__typename
... on PaymentCreditTransaction {
id
capturedAmount {
value
currencyCode
decimalPlaces
}
}
}
}
}
}
}
}
}
Variables
{
"id": "<TRANSACTION_BATCH_ID_OPEN>"
}
⚠️ Please login to execute queries. Visit the dashboard to authenticate.
Result
{
"data": {
"node": {
"__typename": "TransactionBatch",
"id": "<TRANSACTION_BATCH_ID_OPEN>",
"status": "OPEN",
"purchases": {
"__typename": "PaymentTransactionsConnection",
"pageInfo": {
"hasNextPage": false,
"hasPreviousPage": false,
"startCursor": "<CURSOR_PAYMENT_DEBIT_1>",
"endCursor": "<CURSOR_PAYMENT_DEBIT_2>"
},
"edges": [
{
"cursor": "<CURSOR_PAYMENT_DEBIT_1>",
"node": {
"__typename": "PaymentDebitTransaction",
"id": "<PAYMENT_DEBIT_TRANSACTION_ID_1>",
"capturedAmount": {
"value": 6000,
"currencyCode": "USD",
"decimalPlaces": 2
}
}
},
{
"cursor": "<CURSOR_PAYMENT_DEBIT_2>",
"node": {
"__typename": "PaymentDebitTransaction",
"id": "<PAYMENT_DEBIT_TRANSACTION_ID_2>",
"capturedAmount": {
"value": 4000,
"currencyCode": "USD",
"decimalPlaces": 2
}
}
}
]
},
"refunds": {
"__typename": "PaymentTransactionsConnection",
"pageInfo": {
"hasNextPage": false,
"hasPreviousPage": false,
"startCursor": "<CURSOR_PAYMENT_CREDIT_1>",
"endCursor": "<CURSOR_PAYMENT_CREDIT_1>"
},
"edges": [
{
"cursor": "<CURSOR_PAYMENT_CREDIT_1>",
"node": {
"__typename": "PaymentCreditTransaction",
"id": "<PAYMENT_CREDIT_TRANSACTION_ID_1>",
"capturedAmount": {
"value": 2500,
"currencyCode": "USD",
"decimalPlaces": 2
}
}
}
]
}
}
},
"extensions": {
"requestId": "<REQUEST_ID>"
}
}

TransactionBatchEntryFilterInput supports five HQL fields:

FieldTypeOperatorsExample
publicPaymentIdID=, !=publicPaymentId = 'acqt_sale_1'
capturedAmountCurrency amount=, !=, >=, <=, >, <capturedAmount >= 'USD 300'
accountingDirectionDEBIT, CREDIT=, !=accountingDirection = 'DEBIT'
createdAtISO 8601 timestamp=, !=, >=, <=, >, <createdAt >= '2025-12-09T00:00:00Z'
updatedAtISO 8601 timestamp=, !=, >=, <=, >, <updatedAt >= '2025-12-09T00:00:00Z'

Money literals here carry minor units too, so capturedAmount >= 'USD 300' AND capturedAmount <= 'USD 500' matches entries between $3.00 and $5.00.

Batch-level fees

A batch's fees field returns TransactionBatchFee entries — a separate interface from the transaction-level PaymentTransactionFee described in Transaction Fees. TransactionBatchFee declares accountingDirection, description, and feeAmount — one field more than PaymentTransactionFee, which exposes only accountingDirection and feeAmount. It currently has one concrete type, TransactionBatchProcessingFee.

Batch fees are calculated when a batch closes, so they are populated on closed batches and null on an open one — note that the field is null rather than an empty list when a batch has no fees. See the fees selection in Inspect a batch above for a populated example.

Close a batch for payout

Close an open batch on demand with closeTransactionBatch. The close is processed asynchronously: the batch moves to CLOSE_REQUESTED immediately and doesn't reach CLOSED until the async processing completes. Closing also opens a new batch in its place, returned as openedTransactionBatch.

Whether a batch also closes on a schedule depends on its owner. A batch owned by a Payfac closes automatically each day at the globally configured close time, but closing one on demand opts that owner out of the schedule — the replacement batch returned as openedTransactionBatch then stays open until it too is closed on demand. Batches owned by a Merchant, including a payfac's sub-merchants, close automatically only when auto-close is configured for the account; otherwise they stay open until closed on demand.

The input takes transactionBatchId (required), plus optional idempotencyKey (a UUIDv4 that dedupes repeated requests) and externalIdentifier (an external reference, up to 255 alphanumeric characters).

Closing a batch cannot be undone

This mutation runs against your active organization's environment. Closing a batch cannot be undone — there is no reopen operation — and closing a Payfac-owned batch on demand permanently opts that owner out of the daily close schedule.

Close a batch for payout
Query
mutation CloseTransactionBatch($input: CloseTransactionBatchInput!) {
closeTransactionBatch(input: $input) {
__typename
... on AccessDeniedError {
message
}
... on UserError {
errors {
errorPath
code
description
}
}
... on CloseTransactionBatch {
closingTransactionBatch {
id
status
fees {
__typename
accountingDirection
description
feeAmount {
value
currencyCode
decimalPlaces
}
}
}
openedTransactionBatch {
id
status
}
}
}
}
Variables
{
  "input": {
    "transactionBatchId": "<TRANSACTION_BATCH_ID_OPEN>",
    "idempotencyKey": "<IDEMPOTENCY_KEY>",
    "externalIdentifier": "<EXTERNAL_IDENTIFIER>"
  }
}
⚠️ Please login to execute queries. Visit the dashboard to authenticate.
Result
{
"data": {
"closeTransactionBatch": {
"__typename": "CloseTransactionBatch",
"closingTransactionBatch": {
"id": "<TRANSACTION_BATCH_ID_OPEN>",
"status": "CLOSE_REQUESTED",
"fees": [
{
"__typename": "TransactionBatchProcessingFee",
"accountingDirection": "DEBIT",
"description": "Processing fee",
"feeAmount": {
"value": 150,
"currencyCode": "USD",
"decimalPlaces": 2
}
}
]
},
"openedTransactionBatch": {
"id": "<TRANSACTION_BATCH_ID_NEW>",
"status": "OPEN"
}
}
},
"extensions": {
"requestId": "<REQUEST_ID>"
}
}