Skip to main content
Every payment the Router mints lands on one ledger, whatever protocol it used and whichever service it paid. That’s most of the point of routing payments at all: instead of reconciling a dozen provider dashboards, you have a single record of what your agents bought. All ledger endpoints are scoped to the authenticated caller. You only ever see your own payments.

List payments

An invalid from or to returns 400 BCK.ROUTER.0001 rather than silently ignoring the filter.
amount is in the asset’s smallest unit, and assetDecimals is the scale to divide it by. The figure charged against your Delegation cap is the cents equivalent, rounded up, which is why a long run of sub-cent calls costs more budget than the raw amounts suggest.

Reading the asset

Three fields describe what was paid in, because the raw one isn’t the same kind of value on every rail: assetSymbol and assetDecimals are resolved when you read the record rather than stored on it, so rows written before they existed carry them too.
When assetDecimals is null, don’t substitute a default. It is the reliable “we don’t recognise this” signal — assetSymbol can be non-null on the very same row. Treat amount as raw atomic units rather than assuming a scale, and render the (truncated) asset rather than a guessed ticker. Assuming is worse than degrading: a wrong label is visibly wrong, whereas a wrong scale renders a plausible, wrong number and then sits in a spend total next to correctly scaled ones. In particular, not every row is a 6-decimal stablecoin — a card-rail row’s amount is already in cents, at scale 2.

The routing fee on a record

amount is the merchant leg only. If a routing fee applied, it was reserved against your cap on top of that amount, and the record breaks it out:
feeCents: "0" doesn’t mean the payment carried no fee. The two figures round in opposite directions, so a fee can apply in atomic units yet add no cent to your cap reserve — an Accrued, or Released, row can legitimately read feeCents: "0". feeAtomic is the one that answers “did a fee apply”: a fee too small to transfer is dropped outright rather than reserved, so feeAtomic: "0" means no routing fee was charged at all, and the row reads feeStatus: "None".
The routing fee is 5% (feeBps: 500). Two cases legitimately read feeStatus: "None" with a zero fee: an MPP payment, since that rail cannot carry a fee at all, and a mode-A call made without a requestId — without one a retry cannot be deduplicated, so collecting would risk double-charging. Mode B always carries a requestId, so it always collects.
This is why merchant amounts and spent budget don’t reconcile on their own. amount on this record is the merchant leg, so the combined cap charge isn’t derivable from the ledger alone. It’s recorded as amountCents on GET /api/v1/delegation/{id}/transactions, with the fee broken out in that row’s providerMetadata.If the fee is later Released, that row’s amountCents is reduced by the fee and providerMetadata.feeReleased: true is set — but the feeCents breakout stays. So when that flag is present, don’t add the breakout back on top: the total has already moved.

Auditing the fee leg

feeTxHash and feeNonce are what make the fee movement auditable on its own, and each answers a different question. feeTxHash is not txHash. txHash anchors the merchant leg; feeTxHash anchors the fee leg. They are independent movements that settle separately — one can land while the other hasn’t — so reconcile them separately rather than reading either as evidence of the other. Like txHash, it is recorded verbatim as the settling party reported it, so treat it as an anchor to verify rather than as proof, and don’t assume a 0x shape. feeNonce is the key that ties an on-chain transfer back to this payment. EIP-3009 records every authorization under authorizationState(payer, nonce), so with the nonce you can ask the chain directly whether that specific fee movement was consumed — which is exactly how a Submitted fee is resolved. It is not spendable on its own: a nonce identifies an authorization, it isn’t one. The signed authorization itself is deliberately never stored.
A Settled fee can carry feeTxHash: null, and that isn’t a bug. When the fee is confirmed by reading authorizationState rather than by observing the settlement, the chain answers with a boolean — consumed or not — and there is no transaction to record. On those rows feeNonce is the audit key; use it rather than the missing hash.

Fee lifecycle

Released is the one that returns budget to you. It’s reached when the fee provably will not be collected — the facilitator adjudicated and refused, a mode-B hop never returned 2xx, or the fee movement couldn’t be signed — and the fee’s cents go back on your Delegation cap and on the ledgers that mirrored it (an org group budget is corrected separately, and can lag). A fee that applied atomically but added no cent still lands here, with nothing to credit back. It’s only ever reached from Accrued or an adjudicated Failed — never from Submitted or Settled, where money may have moved or definitely did, and handing back headroom for money that already left your wallet is the one direction this must never fail in.
Failed does not imply Released. They are separate steps, and the reserve comes back only on the paths listed above. A fee that failed some other way stays charged against your cap — in particular one that on-chain reconciliation resolved as never consumed — so read feeStatus for the release rather than inferring it from a failure. And an unknown outcome is never released at all — those rows stay Submitted until the chain settles the question.

Export

The CSV carries the same fields as the JSON, and new columns are only ever appended to the header row — so a consumer parsing by column index is unaffected by their arrival.

Aggregate summary

For dashboards and spend monitoring, GET /api/v1/router/payments/summary returns a total plus a time series instead of individual rows.
granularity is day (default), week, or month; an unrecognised value falls back to day rather than erroring. from and to work as above. Buckets are oldest first.
The summary counts payment requests, not amounts. Use the list endpoint when you need money rather than volume.

Record statuses

status describes the merchant leg. A routing fee has its own feeStatus, and the two move independently — see the routing fee on a record.
Issued is not an error. On a paid mode-B call it means the resource came back fine but the settlement anchor didn’t — a missing, oversized, or malformed receipt. The Router deliberately will not fail an already-paid hop over a bad receipt. In mode A it’s simply the state a record sits in until you report the settlement yourself.

Closing a mode-A record

In mode A the Router only signs — it never sees the merchant’s response, so it can’t know whether you redeemed the credential. Report the settlement to close the record:
Take the hash from the merchant’s PAYMENT-RESPONSE header. Two things to know:
  • Only an Issued payment can be settled. Re-reporting the same hash is a harmless no-op; a different hash, or a record that isn’t Issued, returns 409 BCK.ROUTER.0005.
  • A payment id that isn’t yours returns 404 BCK.ROUTER.0004.
Mode B does this for you, which is one of the better reasons to prefer it.

Reconciling against the chain

The settlement reference is reported by the merchant and stored unverified. The Router bounds its length and character set, but it does not confirm on-chain that the transaction exists, that it paid the expected recipient, or that it moved the expected amount. So for anything that matters — accounting, disputes, anomaly detection — treat txHash as an anchor to verify, not as proof. Check it against the chain named in the record’s network field. Note also that a non-0x reference is legitimate: settlement identifiers are protocol-specific, and non-blockchain rails return processor references rather than transaction hashes. Don’t assume the field is always a hash.

Next

Guardrails

Every error code, and what to do about each.

Overview

Back to the top.