Skip to main content

Balances

GET /v1/balances returns the funds you currently hold with each payment provider. The figures are fetched live from each provider at request time — Orchestr does not maintain a ledger — so a response is a point-in-time snapshot and latency depends on the providers queried. Don't call it in a hot path; fetch on demand (a dashboard view, a scheduled reconciliation job), not per transaction.

Balances are reported at three levels, all using the same per-currency shape:

LevelWhereWhat it covers
Accountproviders.<name>.accounts[].balancesOne provider account (one set of provider credentials).
Providerproviders.<name>.aggregatedAll of that provider's accounts combined.
Merchanttop-level aggregatedEverything, across all providers.

At every level each entry reports available (settled, withdrawable), pending (not yet settled), and total — always exactly available + pending.

1. Fetch your balances

This is a GET with no body, so the signed string is just timestamp + path — the buildSignatureHeader helper from the Signing guide is called with an empty body. It assumes you've exported ORCHESTR_SECRET_KEY and ORCHESTR_SIGNING_SECRET as shown in Getting started.

REQ_PATH="/v1/balances"
TS=$(date +%s)
# GET has no body: sign timestamp + path + "" — the query string is NOT signed.
SIG=$(printf '%s' "${TS}${REQ_PATH}" \
| openssl dgst -sha256 -hmac "$ORCHESTR_SIGNING_SECRET" | sed 's/^.*= //')

curl -sS "https://api.sandbox.upprove.com${REQ_PATH}?currency=USD" \
-H "Authorization: Bearer $ORCHESTR_SECRET_KEY" \
-H "X-Signature: t=${TS}, v1=${SIG}"
Sign the path, not the query string

The signed string for a request is timestamp + path + body. For this GET the body is the empty string, and path is /v1/balances without ?currency=... — including the query string in the signature fails with 401 invalid_signature. See Signing.

2. Read the response

A 200 OK returns your balances. The X-Signature response header signs the body — verify it before trusting the response (see Signing).

{
"providers": {
"stripe": {
"accounts": [
{
"mid": "acct_1PqRsT2eZvKY",
"provider_account_id": "pacc_3c2b1a0d",
"enabled": true,
"status": "available",
"channels": [
{"channel_id": "chn_7f8e9d0c", "channel_name": "EU cards", "enabled": true},
{"channel_id": "chn_1a2b3c4d", "channel_name": "UK cards", "enabled": false}
],
"balances": [
{"currency": "EUR", "available": 310.00, "pending": 45.25, "total": 355.25},
{"currency": "USD", "available": 100.50, "pending": 20.00, "total": 120.50}
]
}
],
"aggregated": [
{"currency": "EUR", "available": 310.00, "pending": 45.25, "total": 355.25},
{"currency": "USD", "available": 100.50, "pending": 20.00, "total": 120.50}
]
}
},
"aggregated": [
{"currency": "EUR", "available": 310.00, "pending": 45.25, "total": 355.25},
{"currency": "USD", "available": 100.50, "pending": 20.00, "total": 120.50}
]
}
FieldDescription
providersOne entry per provider, keyed by lowercase provider name (e.g. stripe), sorted alphabetically. Providers that don't support balance retrieval are omitted, so this may be {}.
accountsThe provider accounts holding funds. Channels that share the same provider credentials are merged into one account, so totals are never double-counted.
midYour merchant identifier at the provider.
provider_account_idOrchestr's identifier for the provider account.
enabledtrue if any contributing channel is enabled.
statusavailable or unavailable.
channelsEvery channel contributing to the account — including disabled ones, since funds can remain on a paused channel.
balances / aggregatedPer-currency entries, sorted by currency code. available is settled and withdrawable, pending is not yet settled, and total is always available + pending. No field is ever null — absent components are reported as 0.

Filtering by currency

Pass ?currency=usd to restrict the response to one ISO 4217 currency — the code is case-insensitive. Orchestr re-applies the filter to what each provider returns, so the response honors it even when a provider ignores the filter. An invalid code is rejected with 400 validation_failed and a per-field entry in errors[]:

{
"code": "validation_failed",
"message": "Validation failed",
"request_id": "req_2c1f8a7b9e3d4051",
"errors": [{"field": "currency", "message": "must be a valid ISO 4217 currency code"}]
}

Next steps

  • Transfers — move the balance reported here to a bank account or other destination.
  • Payouts — push funds back to a customer's original payment instrument.
  • Signing — build the request signature and verify the response signature.
  • The full API Reference for GET /v1/balances.