List the invoices due this week

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 EUR

Scopes: 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.

📘

Experimental

Invoice 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:

ParameterValue
dueBefore2026-10-06
paymentStatusunpaid, and a second paymentStatus row with partiallyPaid
stageinReview, and a second stage row with toSettle
sortBydueDate
sortasc

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). Add pendingApproval to the stage filter to also see those still waiting for approval. Disputed invoices are left out: add disputed to see them.
  • Partially paid invoices are included, with only what remains in amountDue.
  • Overdue invoices are included: dueBefore has no lower bound. Add dueAfter with 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_ID to 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.