Inspiration
A small seller gets a PayPal dispute and has days to answer. Fighting takes hours and often loses; refunding everything is expensive. A partial offer is often the cheapest way out, but nobody does the math. I wanted an advisor that does the math, explains itself in plain words, and never moves money without the seller's OK. And because disputes are stressful, I gave it a face: Parley, a snowy owl in a navy waistcoat.
What it does
Parley reads every open dispute through PayPal's Disputes API and webhooks and makes a call on each one: fight it, refund it, or offer a specific amount back. Every call comes with:
- an estimated chance to win, shown as a range, e.g. "23% (5–46%)";
- how similar past cases ended;
- how strong the evidence on file is;
- what the seller will likely keep, in dollars and cents.
On the demo's sleeping-bag case, Parley offers $97.50 back on a $179.00 claim: a 59% chance the buyer takes it, and on average the seller keeps about $32 more than by fighting.
One click shows the exact PayPal call (POST /v1/customer/disputes/{id}/make-offer); a second click sends it. Parley's chat can answer "which disputes are due this week?", stage an offer as a "Needs your OK" card, and remember seller facts ("I always ship with UPS tracking"). Behind the scenes it keeps an activity log of every PayPal call and approval, an all-disputes grid with CSV/JSON export, and a read-only API with an OpenAPI spec.
How I built it
- Front end: React 18 + TypeScript, AG Grid; a mobile layout with the owl docked at the bottom.
- Server: Node; Postgres full-text search over resolved cases finds the precedents.
- Decision core: Rust, integer cents, behind a versioned contract (
decide.v2). - Win chance: a beta posterior from precedents and a per-reason prior, with \( K = 4 \) pseudo-cases: $$ p = \frac{\text{wins} + K\,p_0}{n + K} $$
- Offer amount: expected value of each choice, with \( A \) the amount, \( F \) the fee and \( c \) the cost of fighting: $$ EV_{\text{accept}} = -A, \qquad EV_{\text{fight}} = -(1-p)(A+F) - c $$ $$ EV_{\text{offer}}(x) = q(x)\,(-x) + \big(1-q(x)\big)\,EV_{\text{fight}} $$ where \( q(x) \) is a logistic acceptance curve. Parley scans offers in 50-cent steps and only recommends one when it beats both fighting and refunding.
- AI: a real LLM drafts replies, runs the chat and picks tools. Tool names follow PayPal's Agent Toolkit (
list_disputes,get_dispute,accept_dispute_claim); I addedmake_dispute_offerandprovide_dispute_evidencein the same style. The model never computes money. - Runtime: Kubernetes (k3d) via Nix, or Docker Compose for anyone; webhooks verified through PayPal's verify-webhook-signature.
Challenges I ran into
- Keeping the model out of the arithmetic while still letting it explain the numbers.
- Making the chat resolve "send the offer" to exactly one actionable case, and making its counts match the list's.
- PayPal sandbox disputes have to be opened by a buyer in the sandbox web UI, so the write calls were exercised against a faithful simulator of the Disputes API.
- Running a public demo safely: 10-minute demo sessions and per-visitor rate limits so the AI budget can't be drained.
Accomplishments that I'm proud of
- An offer engine that finds a specific dollar amount, not a fixed percentage.
- Every recommendation shows its working, down to the exact PayPal request.
- An owl people actually want to talk to.
What I learned
Sellers trust a number more when they can open the exact PayPal call and the plain-words reasoning behind it. Honesty about uncertainty ("estimated", with a range) beats false precision.
What's next for Parley
- A real sandbox dispute end to end, and evidence file upload.
- Calibrating the priors on real outcomes.
- An animated Rive owl that reacts to the conversation.
- "Discuss with Parley" on every case, with what-if offers re-run by the engine.
- Exposing Parley's tools as an MCP server.
What is real
The demo runs on a simulated PayPal Disputes API with 60 synthetic disputes. Against the real PayPal sandbox, my app authenticated and listed disputes (HTTP 200). Win chances are estimates from stated assumptions (served at /api/assumptions), not calibrated against PayPal outcomes.
Built With
- ag-grid
- caddy
- claude
- docker
- k3d
- kubernetes
- nix
- node.js
- openai-compatible-api
- openapi
- paypal-agent-toolkit
- paypal-disputes-api
- paypal-transaction-search
- paypal-webhooks
- postgresql
- react
- rive
- rust
- server-sent-events
- typescript
- vite