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:
| Level | Where | What it covers |
|---|---|---|
| Account | providers.<name>.accounts[].balances | One provider account (one set of provider credentials). |
| Provider | providers.<name>.aggregated | All of that provider's accounts combined. |
| Merchant | top-level aggregated | Everything, 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.
- cURL
- Node.js
- Python
- PHP
- Java
- C#
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}"
const path = '/v1/balances';
const res = await fetch(`https://api.sandbox.upprove.com${path}?currency=USD`, {
headers: {
Authorization: `Bearer ${process.env.ORCHESTR_SECRET_KEY}`,
'X-Signature': buildSignatureHeader(process.env.ORCHESTR_SIGNING_SECRET, path, ''),
},
});
const balances = await res.json();
console.log(balances.aggregated);
import requests
path = "/v1/balances"
resp = requests.get(
f"https://api.sandbox.upprove.com{path}",
params={"currency": "USD"},
headers={
"Authorization": f"Bearer {ORCHESTR_SECRET_KEY}",
"X-Signature": build_signature_header(ORCHESTR_SIGNING_SECRET, path, ""),
},
)
balances = resp.json()
print(balances["aggregated"])
<?php
$path = '/v1/balances';
$ch = curl_init("https://api.sandbox.upprove.com{$path}?currency=USD");
curl_setopt_array($ch, [
CURLOPT_RETURNTRANSFER => true,
CURLOPT_HTTPHEADER => [
'Authorization: Bearer ' . getenv('ORCHESTR_SECRET_KEY'),
'X-Signature: ' . build_signature_header(getenv('ORCHESTR_SIGNING_SECRET'), $path, ''),
],
]);
$balances = json_decode(curl_exec($ch), true);
print_r($balances['aggregated']);
String path = "/v1/balances";
HttpRequest request = HttpRequest.newBuilder(URI.create("https://api.sandbox.upprove.com" + path + "?currency=USD"))
.header("Authorization", "Bearer " + System.getenv("ORCHESTR_SECRET_KEY"))
.header("X-Signature", buildSignatureHeader(System.getenv("ORCHESTR_SIGNING_SECRET"), path, ""))
.GET()
.build();
HttpResponse<String> response = HttpClient.newHttpClient()
.send(request, HttpResponse.BodyHandlers.ofString());
System.out.println(response.body());
var path = "/v1/balances";
using var http = new HttpClient();
var request = new HttpRequestMessage(HttpMethod.Get, $"https://api.sandbox.upprove.com{path}?currency=USD");
request.Headers.Add("Authorization", $"Bearer {Environment.GetEnvironmentVariable("ORCHESTR_SECRET_KEY")}");
request.Headers.Add("X-Signature",
BuildSignatureHeader(Environment.GetEnvironmentVariable("ORCHESTR_SIGNING_SECRET"), path, ""));
var response = await http.SendAsync(request);
Console.WriteLine(await response.Content.ReadAsStringAsync());
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}
]
}
| Field | Description |
|---|---|
providers | One 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 {}. |
accounts | The provider accounts holding funds. Channels that share the same provider credentials are merged into one account, so totals are never double-counted. |
mid | Your merchant identifier at the provider. |
provider_account_id | Orchestr's identifier for the provider account. |
enabled | true if any contributing channel is enabled. |
status | available or unavailable. |
channels | Every channel contributing to the account — including disabled ones, since funds can remain on a paused channel. |
balances / aggregated | Per-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.