The wallet balance, what is available or committed, and the latest wallet loads.
Check how much is on your Spendesk wallet, how much is still available to spend, what is already committed, and which loads arrived recently.
Wallet Acme France (EUR)
Balance 98,900.00
Available to spend 97,300.86
Allocated on cards 1,599.14
Scheduled transfers 0.00 (invoices 0.00, expense claims 0.00)
Wallet loads, last 30 days: 1
2026-09-12 wireTransfer 100,000.00 EURScopes: wallet-summary:read, wallet-load:read. With an organisation-level key, add experimental:company:read so the script can list the companies. Set-up: Running the recipes.
With Postman
In the Settlements and wallet folder:
- Get Wallet Summary — no parameter.
totalAmountis the balance,totalAvailableAmountwhat remains to spend,allocatedOnCardswhat is set aside on cards, andscheduledTransfersAmountthe invoice and expense-claim payments already scheduled. Amounts are in cents. - Get Wallet Loads — set
fromto the start of the period as a date and time, for example2026-09-01T00:00:00.000Z. Each load has itsamount,type(wireTransferorcard) andcreatedAt.
With an AI assistant
What is our Spendesk wallet balance, how much is still available, and which wallet loads did we receive in the last 30 days?
With a script
Save it as treasury.py, then run python3 treasury.py — or python3 treasury.py 90 for the loads of the last 90 days.
"""Treasury check: wallet balance, what is committed, and the latest wallet loads.
Usage: python3 treasury.py [DAYS] (loads of the last N days, default 30)
Needs SPENDESK_CLIENT_ID and SPENDESK_CLIENT_SECRET. With an organisation-level key it covers every
company, or only SPENDESK_COMPANY_ID if set.
Scopes: wallet-summary:read, wallet-load:read (and experimental:company:read with an organisation-level key).
"""
import base64, datetime as dt, json, os, sys, urllib.error, urllib.parse, urllib.request
BASE = os.environ.get("SPENDESK_BASE_URL", "https://public-api.spendesk.com")
def token():
creds = base64.b64encode(f"{os.environ['SPENDESK_CLIENT_ID']}:{os.environ['SPENDESK_CLIENT_SECRET']}".encode()).decode()
req = urllib.request.Request(BASE + "/v1/auth/token", data=b"grant_type=client_credentials",
headers={"Authorization": "Basic " + creds, "Content-Type": "application/x-www-form-urlencoded"})
return json.load(urllib.request.urlopen(req))["access_token"]
TOKEN = token()
def api(method, path, query=None, body=None, company=None):
url = BASE + path + ("?" + urllib.parse.urlencode(query, doseq=True) if query else "")
headers = {"Authorization": "Bearer " + TOKEN, "Content-Type": "application/json"}
if company:
headers["X-Company-Id"] = company
req = urllib.request.Request(url, method=method, headers=headers, data=json.dumps(body).encode() if body else None)
return json.load(urllib.request.urlopen(req))
def companies(need_id=False):
"""The companies to cover, as (id, name, X-Company-Id header).
Organisation-level key: every company of the organisation, or only SPENDESK_COMPANY_ID if set.
Company-level key: the key's own company (no header needed)."""
only = os.environ.get("SPENDESK_COMPANY_ID")
try:
found, page = [], 1
while True:
result = api("GET", "/v2/companies", {"page": page, "pageSize": 30})
found += [(c["id"], c["name"], c["id"]) for c in result["data"]]
if page * 30 >= result["meta"]["pagination"]["total"]:
return [c for c in found if not only or c[0] == only]
page += 1
except urllib.error.HTTPError:
if only: # organisation-level key without experimental:company:read
return [(only, only, only)]
own = api("GET", "/v1/users", {"pageSize": 1})["data"][0]["companyId"] if need_id else None
return [(own, "", None)]
days = int(sys.argv[1]) if len(sys.argv) > 1 else 30
since = (dt.datetime.now(dt.timezone.utc) - dt.timedelta(days=days)).strftime("%Y-%m-%dT%H:%M:%S.000Z")
for _, _, header in companies():
# 1. The wallet: amounts are in cents, divide by 100.
w = api("GET", "/v1/wallet-summary", company=header)
print(f"\nWallet {w['name']} ({w['currency']})")
print(f" Balance {w['totalAmount'] / 100:>14,.2f}")
print(f" Available to spend {w['totalAvailableAmount'] / 100:>14,.2f}")
print(f" Allocated on cards {w['allocatedOnCards']['totalAllocatedAmount'] / 100:>14,.2f}")
print(f" Scheduled transfers {w['scheduledTransfersAmount']['totalScheduledTransferAmount'] / 100:>14,.2f}"
f" (invoices {w['scheduledTransfersAmount']['scheduledInvoicesAmount'] / 100:,.2f},"
f" expense claims {w['scheduledTransfersAmount']['scheduledExpenseClaimsAmount'] / 100:,.2f})")
# 2. Wallet loads of the period, 30 per page. A GBP wallet pages with a cursor instead of `page`.
loads, page, cursor = [], 1, None
while True:
query = {"from": since, "page": page, "pageSize": 30}
if cursor:
query["cursor"] = cursor
result = api("GET", "/v1/wallet-loads", query, company=header)
loads += result["data"]
pagination = result["meta"]["pagination"]
if pagination.get("nextCursor"):
cursor = pagination["nextCursor"]
elif cursor or page * 30 >= pagination["total"]:
break
else:
page += 1
print(f" Wallet loads, last {days} days: {len(loads)}")
for load in sorted(loads, key=lambda x: x["createdAt"], reverse=True):
print(f" {load['createdAt'][:10]} {load['type']:<13} {load['amount'] / 100:>14,.2f} {load['currency']}")Good to know
- One wallet per company: with an organisation-level key, the script shows the wallet of every company. Set
SPENDESK_COMPANY_IDto see only one. fromandtotake a date and time (2026-09-01T00:00:00.000Z), not a date alone.- Wallet loads come 30 per page at most: the script reads every page.
- GBP wallets page with a cursor:
pageis ignored andmeta.pagination.totalonly counts the current page. Whilemeta.pagination.hasNextPageis true, sendmeta.pagination.nextCursorascursorwith the samefromandto. The script does it for you.
The requests are Get Wallet Summary and Get Wallet Loads.
Last checked against the Spendesk demo environment on 29 September 2026.