Check the wallet balance

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 EUR

Scopes: 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. totalAmount is the balance, totalAvailableAmount what remains to spend, allocatedOnCards what is set aside on cards, and scheduledTransfersAmount the invoice and expense-claim payments already scheduled. Amounts are in cents.
  • Get Wallet Loads — set from to the start of the period as a date and time, for example 2026-09-01T00:00:00.000Z. Each load has its amount, type (wireTransfer or card) and createdAt.

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_ID to see only one.
  • from and to take 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: page is ignored and meta.pagination.total only counts the current page. While meta.pagination.hasNextPage is true, send meta.pagination.nextCursor as cursor with the same from and to. 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.