Huy VuPortfolio

REST API · Python · Accounting

Bank Matching API

FastAPI service that matches bank lines to bills

  • REST API
  • OAuth / SSO
  • Workflow automation

An internal tool for an accounting firm: staff import a client's bank CSV, a matching engine pairs outgoing payments with the client's open bills, and staff approve, reject or categorise each line. The Python REST API sits behind the Next.js app, which calls it with a short-lived signed token, and every action is audited.

/ What it does

  • [01]Versioned REST API (/api/v1) in FastAPI, with interactive OpenAPI docs at /docs.
  • [02]Service-to-service auth: Next.js signs a short-lived HS256 JWT per request, and the API checks its expiry, issuer, audience and which clients the user may access.
  • [03]Parses five bank CSV layouts (CBA, Westpac, ANZ, NAB, generic); re-uploading a file creates no duplicates.
  • [04]Matching rules: amount equal to the cent, a date window, and fuzzy vendor names that ignore noise like "PTY LTD".
  • [05]Built behind an AccountingRepository interface, so QuickBooks Online can replace the local tables without touching the UI.

/ How it works

From request to result

  • Person
  • API call
  • Data
  1. Staff

    Uploads a client's bank statement CSV

    Next.js server action

  2. Next.js server

    Signs a short-lived JWT; the browser never calls the API

    Authorization: Bearer <HS256 JWT>

  3. FastAPI

    Detects the bank layout, parses to integer cents, de-duplicates

    POST /api/v1/clients/{id}/statements

  4. Matching engine

    Exact / check / no-match against open bills

    POST /api/v1/clients/{id}/reconciliation/run

  5. Staff

    Approves, rejects or categorises each line

    POST …/reconciliation/approve

  6. Audit log

    Every decision recorded with who and when

    GET /api/v1/clients/{id}/audit

/ API surface

  • GET
    /api/v1/clients

    Clients this user may access (from the token's claims)

  • POST
    /api/v1/clients/{id}/statements

    Import a bank CSV (201 Created)

  • POST
    /api/v1/clients/{id}/reconciliation/run

    Run the matching engine

  • POST
    /api/v1/clients/{id}/reconciliation/approve

    Approve matched lines

  • POST
    /api/v1/clients/{id}/transactions/{tid}/categorise

    Assign an account from the chart of accounts

  • GET
    /api/v1/clients/{id}/audit

    Audit trail

/ From the code

backend/app/security.pypython
def get_current_user(creds = Depends(_bearer)) -> CurrentUser:
    if creds is None:
        raise HTTPException(401, "Missing bearer token")
    s = get_settings()
    try:
        claims = jwt.decode(
            creds.credentials,
            s.internal_api_secret,
            algorithms=["HS256"],
            audience=s.jwt_audience,
            issuer=s.jwt_issuer,
            options={"require": ["exp", "sub"]},
        )
    except jwt.PyJWTError:
        raise HTTPException(401, "Invalid or expired token") from None

/ Built to be reliable

  • Money stored as integer cents and parsed with Decimal ("$1,150.00", "(85.50)", "85.50 DR").
  • Client isolation: every row carries client_id, and the token says which clients a user can see.
  • Re-importing the same statement is safe: duplicates are detected.
  • 52 automated tests across parsing, bank layouts, matching rules and the approve flow.
  • Next step: QuickBooks Online over OAuth 2.0, with requestid idempotency on every write.

/ Screens

Bank Matching API screenshot

Next project →

CareRoster

Rostering platform for NDIS providers