Inspiration

56% of US small businesses are owed money on unpaid invoices, $17,500 on average (QuickBooks, 2025). For a freelancer, chasing that money is unpaid admin work, and it's awkward: the client who always pays a few days late needs a gentle nudge, the one who ignored the last two reminders needs a firm one, and you have to keep track of which is which.

PayPal already sends invoices and reminders, but every reminder looks the same no matter who the client is. Invoice Chaser adds the part a freelancer currently does in their head: who is likely to pay late, what to say to each client, and how much cash is actually coming in over the next few weeks.

What it does

Invoice Chaser is an accounts-receivable assistant for freelancers who bill through PayPal.

  • AR board (AG Grid). KPI tiles and an aging bar on top, then every unpaid invoice sorted most urgent first. Switch to a by-client view with merged cells or sort by due date, filter by Unpaid / Overdue / Draft ready / Paid, search, and change row density. Client notes are edited inline in the grid.
  • Payer risk score (0–100) for each open invoice, computed from that client's own payment history.
  • Reminder agent. Claude drafts each reminder from the risk score, days overdue, reminders already sent, and your note on the client ("Long-term client, be warm", "Enterprise AP team, always quote the invoice number"). The tone steps up from friendly to firm to final. Open the review drawer to see the facts and timeline, override the tone, edit the draft or ask for a rewrite. Nothing is sent until you approve it.
  • Invoice from chat. Type "Invoice Acme $1,200 for the October retainer, due in 14 days", check the preview, and a real PayPal invoice is created and sent.
  • Cash-flow forecast of expected incoming cash for each of the next four weeks.
  • Live updates. When a client pays, a PayPal webhook updates the board.

How I built it

Stack: Python, FastAPI and SQLModel on SQLite for the backend; React, Vite and AG Grid Community for the frontend. It ships as one Docker image (API plus built UI) with a Render blueprint.

PayPal Invoicing API v2 does the real work:

What API
Sync invoices GET /v2/invoicing/invoices, GET /v2/invoicing/invoices/{id}
Create and send from chat POST /v2/invoicing/invoices → POST /v2/invoicing/invoices/{id}/send
Send an approved reminder PUT /v2/invoicing/invoices/{id} → POST /v2/invoicing/invoices/{id}/remind
Seed payment history POST /v2/invoicing/invoices/{id}/payments
Live updates Webhooks, verified with POST /v1/notifications/verify-webhook-signature

Claude (claude-opus-5-5) with structured outputs handles two jobs: writing reminders and turning a chat message into invoice fields. The reminder prompt only allows facts that are passed in, so the model can't invent amounts, dates or consequences. Every model call goes through one function, so switching providers means editing one place.

Risk score. A hand-weighted heuristic over four signals: the client's average days late \( \bar{d} \), the share of their invoices paid late \( s \), how much bigger this invoice is than their usual one \( o \) (capped at 1), and the invoice's age in days \( a \):

$$ \text{risk} = 40 \min\left(\frac{\bar{d}}{30}, 1\right) + 30\, s + 15\, o + 15 \min\left(\frac{a}{60}, 1\right) $$

Both history terms are smoothed (one imaginary invoice 5 days late, Laplace smoothing on the late share), so a brand-new client starts at "unknown" rather than "perfect".

Tone. Start at friendly and step up one level for each reminder already sent, for a risk of 60 or more, and for being more than 30 days overdue, capped at final.

Forecast. Each open invoice is expected in the week of its due date plus the client's usual lateness, weighted by the chance it gets paid at all, \( 1 - 0.6 \cdot \text{risk}/100 \), and spread 25/50/25 across that week and its neighbours.

The risk, tone and forecast logic are pure functions with tests.

Challenges I ran into

  • PayPal ignores custom reminder text. The /remind and /send endpoints accept a subject and note, but PayPal always sends its own email text. The workaround: when you approve a reminder, the app first writes the AI-drafted message into the invoice's note with a PUT, then calls /remind. The client sees the personalised message when they open the invoice from PayPal's reminder email. PayPal also allows at most 2 reminders per invoice per day, so the app works within that limit.
  • Grouping by client without AG Grid Enterprise. Row grouping is an Enterprise feature, so the by-client view uses Community's spanRows to merge client cells instead.
  • Realistic demo data. A risk score is only interesting with history, so the seed script creates 6 clients and about 30 sandbox invoices, then records backdated payments through the payments endpoint to build each client's late-payment pattern.
  • Cold start. With no smoothing, a client with no history would score as a perfect payer, which is the wrong default for someone you've never billed.

Accomplishments that I'm proud of

  • Reminders that read like the freelancer wrote them, adjusted to each client, while the freelancer approves every one.
  • Everything runs against real PayPal sandbox invoices: creating, sending, reminding, and live payment updates through verified webhooks.
  • A forecast that accounts for each client's habits instead of assuming everyone pays on the due date.

What I learned

  • Read the API spec before designing around a field. The reminder workaround came from checking PayPal's OpenAPI spec and then confirming the behaviour in the sandbox.
  • Small, explainable scoring beats a black box when the user has to trust the result and act on it.
  • Keeping the human in the loop for anything that emails a client is what makes an AI agent usable for this job.

What's next for Invoice Chaser

  • PayPal partner permissions, so many freelancers can connect their own accounts instead of one set of sandbox credentials.
  • Replace the hand-tuned risk weights with logistic regression once there are a few hundred paid invoices to learn from.
  • Scheduled reminder suggestions, so drafts are waiting for review when an invoice crosses its due date.

Built With

Share this project:

Updates

Submission history