The approved supplier invoices to pay in the next days, overdue ones first, with the total still due per currency.
See which approved supplier invoices you have to pay in the next seven days — and which are already overdue — with the amount still due and the total per currency.
2026-09-26 (overdue) Office Supplies Ltd FA-2026-3391 1,944.00 EUR
2026-10-02 Cloud Hosting Inc INV-88412 2,490.00 EUR
2 invoices due by 2026-10-06: 4,434.00 EURScopes: experimental:invoice:read, and to find the companies: experimental:company:read with an organisation-level key, or user:read with a company-level key. Set-up: Running the recipes.
ExperimentalInvoice endpoints are experimental: their scope is granted on request, and they may still change. See Versioning & Deprecation.
With Postman
In the Invoices and intakes folder, open List invoices and set these query parameters — change the date to seven days from today:
| Parameter | Value |
|---|---|
dueBefore | 2026-10-06 |
paymentStatus | unpaid, and a second paymentStatus row with partiallyPaid |
stage | inReview, and a second stage row with toSettle |
sortBy | dueDate |
sort | asc |
Each invoice shows its supplier.name, invoiceNumber, dueDate and amountDue — what remains to pay, in cents. The company ID in the path comes from your environment's companyId; with a company-level key, Get Users shows it as companyId on every user.
With an AI assistant
Which approved supplier invoices are due in the next 7 days or already overdue? Give me the supplier, the amount still due and the due date, and the total per currency.
With a script
Save it as due_invoices.py, then run python3 due_invoices.py — or python3 due_invoices.py 14 for the next 14 days.
"""List the supplier invoices to pay in the next N days (default 7), overdue ones included.
Usage: python3 due_invoices.py [DAYS]
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: experimental:invoice:read, and experimental:company:read (organisation-level key) or
user:read (company-level key) to find the company.
"""
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 7
cutoff = dt.date.today() + dt.timedelta(days=days)
query = {"dueBefore": str(cutoff), "paymentStatus": ["unpaid", "partiallyPaid"], "stage": ["inReview", "toSettle"],
"sortBy": "dueDate", "sort": "asc", "limit": 100}
for company_id, company_name, _ in companies(need_id=True):
# 1. Approved invoices not fully paid, due before the cutoff, earliest first.
# The company goes in the path of v2 endpoints.
invoices, cursor = [], None
while True:
page = api("GET", f"/v2/companies/{company_id}/invoices", dict(query, **({"cursor": cursor} if cursor else {})))
invoices += page["data"]
cursor = page["meta"]["pagination"]["nextCursor"]
if not cursor:
break
# 2. Print them, then the total still due per currency. Amounts are in cents: divide by 100.
if company_name:
print(f"\n== {company_name}")
totals = {}
for inv in invoices:
late = " (overdue)" if inv["dueDate"] < str(dt.date.today()) else ""
print(f"{inv['dueDate']}{late} {inv['supplier']['name']:<30} {inv['invoiceNumber']:<20} "
f"{inv['amountDue'] / 100:>12,.2f} {inv['currency']}")
totals[inv["currency"]] = totals.get(inv["currency"], 0) + inv["amountDue"]
print(f"{len(invoices)} invoices due by {cutoff}:",
", ".join(f"{amount / 100:,.2f} {currency}" for currency, amount in sorted(totals.items())) or "nothing")Good to know
- Only approved invoices are listed: those waiting for the controller review (
inReview) and those reviewed and waiting for payment (toSettle). AddpendingApprovalto thestagefilter to also see those still waiting for approval. Disputed invoices are left out: adddisputedto see them. - Partially paid invoices are included, with only what remains in
amountDue. - Overdue invoices are included:
dueBeforehas no lower bound. AdddueAfterwith today's date to leave them out. - Several companies: with an organisation-level key, the script lists the invoices of every company, one block per company, each with its own totals. Set
SPENDESK_COMPANY_IDto see only one. - Invoices and payables can disagree. This recipe reads the invoices. The AI assistant, and Search Payables, read the payables instead — and on some accounts the two do not match: an invoice can show as approved and unpaid while no payable exists yet, or while its payable is already paid. Before a payment run, check an invoice that looks wrong in Spendesk.
The request is List invoices; the company in the path works as described in Organisation-level access.
Last checked against the Spendesk demo environment on 29 September 2026.