Skip to content
Dylan San GabrielDylan
Get in touch

GET /api/work/internal-erp/quickbooks

Keeping the books and the app in agreement

Live · · sole engineer

The sales system and the accounting books have to agree to the penny. This is the connection that keeps them agreeing, and the logbook that ended the false alarms.A two-way QuickBooks Desktop integration over the Web Connector, plus a claims ledger so a finance discrepancy is refuted once, not rediscovered monthly.

  • QuickBooks Desktop
  • QBWC / SOAP
  • TypeScript
  • PostgreSQL
invoice linking
Hourly
sync direction
2-way
settled claims
Ledgered
GET/api/work/{id}

Parameters

Request

curl "https://portfolio.dylansg0318.workers.dev/api/work/internal-erp/quickbooks"
source: src/content/projects/{id}

Response200 OK · 2.9 KB

{
  "id": "internal-erp/quickbooks",
  "title": "Keeping the books and the app in agreement",
  "status": "live",
  "year": 2026,
  "category": "work",
  "role": "sole engineer",
  "summary": "A two-way QuickBooks Desktop integration over the Web Connector, plus a claims ledger so a finance discrepancy is refuted once, not rediscovered monthly.",
  "summary_plain": "The sales system and the accounting books have to agree to the penny. This is the connection that keeps them agreeing, and the logbook that ended the false alarms.",
  "stack": [
    "QuickBooks Desktop",
    "QBWC / SOAP",
    "TypeScript",
    "PostgreSQL"
  ],
  "metrics": {
    "invoice_linking": "Hourly",
    "sync_direction": "2-way",
    "settled_claims": "Ledgered"
  },
  "parent": {
    "id": "internal-erp",
    "title": "The internal ERP that runs a dental supply company",
    "page": "https://portfolio.dylansg0318.workers.dev/projects/internal-erp"
  },
  "problem": "QuickBooks is the real books. The app is where the work happens. Invoices, bills, payments and credits had to exist in both, agree, and stay linked, through a Desktop product whose only integration surface is a SOAP web connector that polls you. And the same false discrepancy kept being \"found\": accounts receivable reported at $18M against a real $2M, because a legacy ledger was being summed that was never a balance.",
  "unique": "Two things. Every transaction pushed to QuickBooks gets a row in a link table, so \"is this in QB?\" is a lookup, not a guess, and a document voided in the app is voided in QB rather than deleted, so the trail survives. Second, a finance claims ledger: every discrepancy anyone has ever reported, with its canonical query and whether it was refuted, confirmed or is still open, with evidence. New investigations start from the ledger's filters instead of rediscovering the traps.",
  "ai": "The QBWC protocol work, request/response shapes, session ticketing, the cases where QuickBooks returns a success code and does nothing, was agent-drafted from documentation I captured into the repo first. Every posting rule was reviewed by a second model before it ran against the live company file, because a wrong journal entry is expensive to find and worse to undo.",
  "learned": [
    "Bad filters fail open. A status check written as <> 'void' matched every row because the stored value was 'voided'. The canonical WHERE clauses now live in one place and every report reads from it.",
    "Voided journal entries are unbalanced on purpose, so a trial balance that forgets to exclude them goes red. That's a feature. It makes the missing filter visible instead of quietly wrong.",
    "For direct orders, QuickBooks is the only place money actually moves. The app records. It never pretends to collect. Being clear about which system is the bank stopped a whole category of double-counting."
  ],
  "page": "https://portfolio.dylansg0318.workers.dev/projects/internal-erp/quickbooks"
}

Live: this is the answer from this site’s API, not a mock. Send re-runs the request with your parameters; if the answer names a page, it opens.

The problem

QuickBooks is the real books. The app is where the work happens. Invoices, bills, payments and credits had to exist in both, agree, and stay linked, through a Desktop product whose only integration surface is a SOAP web connector that polls you. And the same false discrepancy kept being "found": accounts receivable reported at $18M against a real $2M, because a legacy ledger was being summed that was never a balance.

What was unique

Two things. Every transaction pushed to QuickBooks gets a row in a link table, so "is this in QB?" is a lookup, not a guess, and a document voided in the app is voided in QB rather than deleted, so the trail survives. Second, a finance claims ledger: every discrepancy anyone has ever reported, with its canonical query and whether it was refuted, confirmed or is still open, with evidence. New investigations start from the ledger's filters instead of rediscovering the traps.

Where AI fit in

The QBWC protocol work, request/response shapes, session ticketing, the cases where QuickBooks returns a success code and does nothing, was agent-drafted from documentation I captured into the repo first. Every posting rule was reviewed by a second model before it ran against the live company file, because a wrong journal entry is expensive to find and worse to undo.

GET/api/work/{id}

Parameters

Request

curl "https://portfolio.dylansg0318.workers.dev/api/work/internal-erp/quickbooks?fields=problem%2Cunique%2Cai"
source: src/content/projects/{id}

Response200 OK · 1.4 KB

{
  "problem": "QuickBooks is the real books. The app is where the work happens. Invoices, bills, payments and credits had to exist in both, agree, and stay linked, through a Desktop product whose only integration surface is a SOAP web connector that polls you. And the same false discrepancy kept being \"found\": accounts receivable reported at $18M against a real $2M, because a legacy ledger was being summed that was never a balance.",
  "unique": "Two things. Every transaction pushed to QuickBooks gets a row in a link table, so \"is this in QB?\" is a lookup, not a guess, and a document voided in the app is voided in QB rather than deleted, so the trail survives. Second, a finance claims ledger: every discrepancy anyone has ever reported, with its canonical query and whether it was refuted, confirmed or is still open, with evidence. New investigations start from the ledger's filters instead of rediscovering the traps.",
  "ai": "The QBWC protocol work, request/response shapes, session ticketing, the cases where QuickBooks returns a success code and does nothing, was agent-drafted from documentation I captured into the repo first. Every posting rule was reviewed by a second model before it ran against the live company file, because a wrong journal entry is expensive to find and worse to undo.",
  "page": "https://portfolio.dylansg0318.workers.dev/projects/internal-erp/quickbooks"
}

Live: this is the answer from this site’s API, not a mock. Send re-runs the request with your parameters; if the answer names a page, it opens.

The story

The company’s real books live in QuickBooks; that’s what the accountant and the IRS see. The day-to-day selling happens in the system I built. Those two have to agree to the penny, forever, and neither was designed to talk to the other.

I built the bridge. Every invoice and bill made on our side gets sent into QuickBooks and linked, so “did this make it into the books?” has a yes-or-no answer instead of a shrug. Cancelled invoices get voided, never deleted, so the paper trail stays intact.

The part I’d point to is a logbook of every scary finding anyone has raised. Early on, a check of the books said customers owed us $18 million; the real number was about $2 million. The query behind it counted the wrong pile. It cost a day to untangle, and the same false alarm kept coming back every few months as new eyes made the same mistake. So now every investigated claim is written down with its verdict and proof. A scary number gets checked against the logbook first, and false alarms die once instead of monthly.

Why Desktop, and why it’s hard

The business runs QuickBooks Desktop, not Online. Desktop’s integration story is the Web Connector: a small Windows service that polls your SOAP endpoint on a schedule, asks “anything for me?”, and carries your qbXML requests into the company file. You never call QuickBooks. It calls you.

That flips every assumption a normal integration makes. You can’t push a bill and check the result. You queue it, wait for the next poll, and reconcile after. So the design is: stage everything, link everything, and make “did it land?” a question the database can answer.

Every document the app sends — invoice, bill, credit memo, payment — gets a row recording what we sent, what QuickBooks assigned, and when. That table is the only answer to “is this in QB?”. Not a flag on the order, not a status field that drifts: a row that exists because the round-trip completed. Voids follow the same rule. An invoice voided in the app becomes a voided invoice in QuickBooks, never a deleted one, so the number stays reserved and the trail stays readable.

The claims ledger

Finance work has a specific problem: the same discrepancy gets found over and over. Someone sums the wrong ledger and reports AR at $18 million. Three months later someone else does the same thing, and each time it costs a day.

So there’s a ledger of claims. Each entry is a discrepancy someone reported, the canonical query that tests it, and a verdict of refuted, confirmed or open, with the evidence. New investigations start from the ledger’s standing filters. If a finding matches a refuted claim, the finding reproduced the mistake, not the problem. Claims never get deleted; the refutations are the most useful rows, because they’re the traps written down.

Which system is the bank

For marketplace orders the marketplace collects the money and pays out on a schedule, and the app reconciles payouts against orders. For direct orders, a dental office calling in, the only place money actually moves is QuickBooks. The app records the sale, links the invoice, and never pretends to have collected anything. Saying that boundary out loud ended a whole family of double-counting bugs where the app and the books each thought the other had the payment.

What it reconciles now

Marketplace invoices link to their QuickBooks counterparts hourly, self-rechecking. Accounts payable is a read-only mirror of QuickBooks bills, so the app can show what we owe without being a second source of truth for it. Cost of goods, journal entries per leg of a return, and the reports that matter — P&L, balance sheet, trial balance — read from one set of canonical filters the claims ledger keeps honest.

What I learned

  1. Bad filters fail open. A status check written as <> 'void' matched every row because the stored value was 'voided'. The canonical WHERE clauses now live in one place and every report reads from it.
  2. Voided journal entries are unbalanced on purpose, so a trial balance that forgets to exclude them goes red. That's a feature. It makes the missing filter visible instead of quietly wrong.
  3. For direct orders, QuickBooks is the only place money actually moves. The app records. It never pretends to collect. Being clear about which system is the bank stopped a whole category of double-counting.
GET/api/work/{id}

Parameters

Request

curl "https://portfolio.dylansg0318.workers.dev/api/work/internal-erp/quickbooks?fields=learned"
source: src/content/projects/{id}

Response200 OK · 721 B

{
  "learned": [
    "Bad filters fail open. A status check written as <> 'void' matched every row because the stored value was 'voided'. The canonical WHERE clauses now live in one place and every report reads from it.",
    "Voided journal entries are unbalanced on purpose, so a trial balance that forgets to exclude them goes red. That's a feature. It makes the missing filter visible instead of quietly wrong.",
    "For direct orders, QuickBooks is the only place money actually moves. The app records. It never pretends to collect. Being clear about which system is the bank stopped a whole category of double-counting."
  ],
  "page": "https://portfolio.dylansg0318.workers.dev/projects/internal-erp/quickbooks"
}

Live: this is the answer from this site’s API, not a mock. Send re-runs the request with your parameters; if the answer names a page, it opens.