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 UPPROVE_SECRET_KEY and UPPROVE_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 "$UPPROVE_SIGNING_SECRET" | sed 's/^.*= //')

curl -sS "https://api.sandbox.upprove.com${REQ_PATH}?currency=USD" \
-H "Authorization: Bearer $UPPROVE_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.