Case study · TimbraBot · Bank-to-tax reconciliation

Closing the month is a data problem. We built the system that solves it.

TimbraBot reconciles bank movements against the tax record. It reads the statement from any bank, proposes every match with the evidence behind it, and rules on deductibility invoice by invoice. A person approves. Nobody types.

Reply within one business day · No commitment

02

The problem

An accounting firm's month-end close rests on a comparison nobody wants to do by hand: every movement on every bank account, matched against every invoice the company issued or received. For a single client with several accounts, that is hundreds of lines a month. For a firm carrying fifteen clients, it is thousands — and all of them land in the same two weeks.

The work is mechanical but not simple. A transfer out of the account may correspond to one invoice, to a partial payment against an invoice issued months earlier, to a payroll dispersal covering forty employees, or to money moving between two accounts the client owns, which corresponds to no invoice at all. The person doing it reads a bank description written for a teller, not for an accountant, and has to decide which of four thousand invoices it belongs to.

What makes it expensive is not the hours. It is that the hours are spent by the most experienced people in the firm, during the only window when they cannot be spent on anything else. Close is a bottleneck by construction: it cannot start until the statements arrive and it cannot slip past the filing deadline. Every client the firm adds widens the same two-week crunch.

And the error is not cosmetic. A movement matched to the wrong invoice produces a tax position that is wrong, in a filing that is signed. A payment recorded against an invoice that was later cancelled produces a deduction that will not survive review. The cost of being wrong is not rework — it is exposure.

03

Why it was hard

The first barrier is the document. A bank statement in Mexico is a PDF designed to be read by a human holding it, and every institution designs its own. Columns move. Debits and credits are sometimes two columns, sometimes one column with a sign, sometimes a letter at the end of the line. Headers repeat mid-page. A single transaction wraps across three lines. Some institutions export a layout that is closer to a mainframe print job than to a table. There is no API, no schema, and no standard — only the file the client downloaded.

The second barrier is that the tax record is not a flat list of invoices. An invoice paid in one shot settles against a single movement. An invoice on credit settles against payment complements — separate fiscal documents, each covering part of the balance, sometimes several of them applied to the same invoice on different dates, sometimes one payment covering several invoices at once. Payroll is issued per employee and paid in one aggregate dispersal. Money moved between the client's own accounts looks exactly like a payment to a supplier. An invoice can be cancelled at the tax authority after it was already reconciled, and a substitute issued in its place.

The third barrier is that the answer is a judgment, not a lookup. Amounts rarely match to the cent, dates rarely align, and the counterparty name in a bank description is an abbreviation someone typed into a transfer form. Two different suppliers can produce the same number on the same day. A system that matches on amount alone is confidently wrong at scale, which is worse than being slow.

This is why an ERP does not solve it. An ERP consumes structured data and assumes the hard part is already done. The hard part is upstream: turning a document that was never meant to be parsed into a reliable ledger, and then reasoning over two document universes that were designed by different institutions for different purposes and never meant to be joined. Generic reconciliation tools solve the last mile of that problem and hand back the first.

04

The TimbraBot program

TimbraBot is an automation program in three phases. Each one stands on the one before it.

Phase 1 — Reading

The problem it solves: the source data does not exist in usable form until someone produces it.

The system ingests bank statements as they come, in whatever layout the institution decided to use, and extracts every movement into a normalized ledger: date, description, amount, direction, balance. Amounts are handled in cents, never in floating point. The extraction is then checked against the statement's own arithmetic — opening balance, movements, closing balance — and any statement whose balances do not add up is flagged as unverified rather than accepted. Unverified movements are excluded from every downstream metric, so a bad read degrades coverage instead of silently corrupting the result.

On the fiscal side, the system takes in the company's tax record — issued and received invoices, payment complements, payroll receipts — and normalizes it into the same ledger discipline, keyed so that the same fiscal document can legitimately exist as an issued invoice for one company and a received invoice for another.

Source documents are retained, so any statement can be re-read without asking the client for the file again. Re-reading is content-addressed: if the document and the extraction logic have not changed, the system does not spend a second read on it.

What changes for the user: they upload a file and get a ledger. They never transcribe, and they are told plainly when a document did not come out clean.

Statement upload screen in dark theme, showing the selected file and its processing status.

Phase 2 — Reconciliation at scale

The problem it solves: matching thousands of movements against thousands of invoices, correctly, in the time available.

The system passes every movement through an ordered engine. First it sets aside what does not correspond to an invoice at all — bank fees, taxes, and mirror transfers between the client's own accounts, which it identifies by finding the matching charge and credit across two accounts the same company owns. Then it resolves payment complements, grouping each payment node against the invoices it settles and checking that the direction is right: a payment the company received must land as a credit, one it made as a charge. Then payroll, first as an aggregate dispersal against the sum of that period's receipts, then employee by employee. Then invoices paid in one shot, by amount, by counterparty directory, by fiscal ID extracted from the bank description, and by name similarity when the description carries an abbreviation instead of a name.

Whatever the deterministic engine cannot resolve goes to a reasoning layer, which sees only the remaining movements and the invoices that are still open, and proposes matches with a stated confidence. Every proposal is then re-checked server-side by code that has the authority to reject it: the invoice must be one that was actually offered, the direction must be right, the amount must fall inside the tolerance for that match type, the date must be within range, a partial application must carry the amount applied, and the counterparty on the bank side must not contradict the invoice. The same invoice cannot be consumed twice.

A proposal is only accepted without human review when confidence is high and there is hard evidence in the description — the counterparty's fiscal ID or legal name. Everything else is presented to the accountant as a proposal with its evidence attached: which invoices, which folio numbers, how the amount breaks down, and why the system believes it.

Every decision the accountant makes feeds back. An approval teaches the system that a given description belongs to a given counterparty; that memory is applied on the next run. A rejection teaches it the opposite. Corrections and cancellations are unwound cleanly rather than being marked rejected, so the system does not learn a negative from a bookkeeping action.

What changes for the user: they stop searching and start deciding. The queue is ordered by confidence, decisions can be taken in batch, and the work that remains is the work that actually required a person.

Phase 3 — Fiscal intelligence

The problem it solves: a reconciled month is not the same as a defensible one.

On top of the reconciled result, the system rules on each expense invoice: deductible, at risk, or not deductible — and attaches the reason. The rules are the ones that decide the outcome in practice. An expense above the legal cash threshold must have been paid electronically. A cancelled invoice does not sustain a deduction. An invoice on credit is only settled when the evidence says so, and the system grades that evidence: a payment complement that closes the balance, a complement that covers only part of it, a payment the bank confirms with no complement issued, a payment still pending confirmation, or nothing at all. The recipient's fiscal ID has to be the company's own. The declared payment method has to survive contact with what the bank actually shows.

Payments made outside the bank account are handled explicitly. An invoice settled in cash, by card, or through a reimbursement can be marked as such with a note, and that evidence enters the same ladder — visibly weaker than a bank-confirmed payment, and never presented as equivalent.

Invoices cancelled at the tax authority after the fact are surfaced with the substitute proposed from hard data — the substitution folio the authority itself carries — so the link can be moved to the replacement, released, or kept, as a decision rather than a discovery.

When a verdict cannot be reached because an input is missing, the system says so. It never manufactures a verdict from absence.

What changes for the user: the firm stops finding problems in the filing and starts finding them in the month. Every verdict carries the reason, so it can be defended or corrected before it matters.

Deductibility block in the monthly view: per-invoice verdicts and the expanded reason for a case flagged at risk.

Illustrative data. No screenshot shows real taxpayer information.

05

Where AI is irreplaceable

Most of this system is deterministic code, deliberately. Two problems inside it cannot be solved that way, and those are the two where a model is doing work no rule could do.

Reading a document that has no format

There is no standard for a Mexican bank statement. Each institution lays out its own, changes it without notice, and owes nobody a schema. Template-based extraction works until the bank moves a column, and then it fails silently — which in this domain is the worst possible failure, because a missing transaction does not announce itself.

The system reads the statement the way a person would: it looks at the page and works out where the movements are, which column is the charge and which is the credit, how the institution encodes direction, and where a transaction that wrapped across lines actually ends. It handles layouts that are not tables at all. It is not tuned per bank; it is written to reason from what is on the page, which is what makes a new institution a non-event instead of a project.

That capability is only safe because of what surrounds it. The extraction is verified against the statement's own arithmetic in cents, and deduplication is aware of which fields are actually unique in that layout — a reference number that identifies a card terminal rather than a transaction cannot be used to collapse two real sales. If the numbers do not close, the statement is marked unverified and its movements are kept out of every metric. The model does the reading. Arithmetic decides whether the reading is trustworthy.

The rules compiler

This is the piece that changes what the system is.

Every accounting firm carries knowledge that lives in one person's head: the transfers to that name are rent, not a supplier · anything from that account is a loan repayment and never has an invoice · when the description says PROV followed by a number, it is that supplier. That knowledge is the difference between a system that reconciles half the month and one that reconciles most of it. In every product that has tried to capture it, it goes into a rules builder with dropdowns and boolean operators — and the accountant, who has the knowledge, does not use it, because the interface was designed by someone who thinks in conditions.

In TimbraBot the accountant writes the rule in Spanish, in a text box, the way they would explain it to a junior. The system compiles it. The output is not a prompt and not a suggestion: it is an executable descriptor that the deterministic engine applies on every run, before any reasoning layer is involved. What compiles cleanly becomes deterministic logic. What is genuinely ambiguous is carried into the reasoning layer as context instead of being discarded. Rules are never deleted when turned off — they are neutralized, so the history of what the firm decided stays intact.

The result is that domain expertise enters the engine without a developer in the loop, and the system that ends the year is measurably better than the one that started it — not because the model improved, but because the firm taught it.

The firm's rules editor: rules written as plain Spanish sentences in a text field, next to their compiled state.

Illustrative data. No screenshot shows real taxpayer information.

06

Architecture

The system is built as four independent stages with a database as the contract between them. Nothing calls a model without a deterministic layer in front of it and a validation layer behind it.

ORIGINBank statementTax recordOther originINGESTIONPrivate storageExtractionmodel reading + arithmetic checkNormalized ledgermovements + fiscal documentsENGINEClassificationMirror transfersPayment complementsAggregate payrollIndividual payrollSingle-payment invoicesCompiled rulesLearned memoryresidueReasoning layerVALIDATIONValidationidentity · direction · amount · date · counterparty · single consumptionrejectedacceptedAccountant's queueLinkapprove / correct / rejectMemory (triggers)FISCALDeductibility engineVerdict + reasons + hash of inputs

Ingestion

Source documents land in private storage and are processed by an extraction service that produces normalized movements — not files — for everything downstream. The engine never sees a PDF. This is the seam that makes the origin of a document irrelevant: a statement uploaded by a user, a fiscal record pulled from the tax authority, and a file delivered by any other route all enter through an adapter and leave as the same normalized ledger. Extraction is content-addressed, so re-processing a document that has not changed costs nothing. Long extractions are accepted asynchronously and reported back by polling, so no browser and no proxy sits on the critical path.

Reconciliation engine

An ordered pipeline of deterministic steps — classification, mirror transfers, payment complements grouped by payment node, aggregate payroll, individual payroll, and single-payment invoices resolved by amount, counterparty directory, fiscal ID extraction and name similarity. Each step consumes what the previous one left. Compiled firm rules are applied here, as deterministic descriptors, not as hints.

Reasoning layer

Only the residue reaches it, in bounded chunks, with a candidate pool trimmed to what is still open. It returns proposals with confidence and a stated rationale.

Validation layer

Server-side code with veto authority over every proposal: identity, direction, amount tolerance by match type, date window, partial-application amount, counterparty agreement, and single-consumption of each invoice within and across chunks. Auto-approval requires high confidence and hard evidence in the description. Everything else becomes a proposal for a person. The rationale shown to the user is generated from the validated data — folio numbers and the amount breakdown — not from the model's prose.

Memory and rules

Approvals and rejections are captured by database triggers into a descriptor store that is consulted on the next run. Rules written in Spanish are compiled into that same descriptor format, which is why firm knowledge and learned knowledge are applied by identical machinery.

Fiscal layer

A deterministic rules engine evaluates deductibility over the reconciled result and materializes the verdict with its reasons and a hash of the inputs that produced it. When any input changes — a match approved, a cancellation processed, a payment recorded — the verdict is recomputed. A rule change invalidates the whole cache by construction, so a stale verdict cannot survive a change in the rules that produced it.

Production

Web application on managed hosting, Postgres with row-level security and invite-only authentication, private object storage for source documents, and a self-hosted orchestration layer for the four backend pipelines. Webhooks are authenticated with signed per-session tokens; there is no unauthenticated path into the pipelines. Concurrency is guarded at the database level so the same company cannot be processed twice at once, and a run that dies is swept rather than blocking the company forever.

07

How it operates today

TimbraBot runs in production at BW Asesores, an accounting firm in Mexico City, where a team of seven uses it across fifteen client companies.

The working rhythm follows the close. Statements come in as the banks publish them and are uploaded as they arrive; the firm's tax records are loaded per company. When a company's month is complete, a user runs reconciliation from the dashboard and the system works through the whole period at once.

What the user gets back is not a report. It is a queue, ordered by confidence, of proposals to decide on — each one showing the movement, the invoices it would settle, the folio numbers, and how the amount breaks down. High-confidence proposals with hard evidence are already applied. The rest are approved, corrected or rejected, in batch when they are obvious and one at a time when they are not. A monthly view carries the same work organized by period, with the deductibility verdicts for that month alongside.

Approvals are always made by the firm's own staff. That is a rule of the deployment, not a limitation of the system: every approval teaches the memory, so an approval made by anyone else would be teaching the engine something the firm did not decide.

Reconciliation queue with proposals ordered by confidence and several checkboxes selected for a batch decision.
Detail of a proposal: the bank movement, the invoices it would settle, their folio numbers and the amount breakdown.
Dashboard listing the companies managed on the platform, each with its reconciliation progress.

Illustrative data. No screenshot shows real taxpayer information.

08

Results

Client companies managed on the platform
15
Bank movements processed
5,282
Statements read
199, across 54 accounts at 10 financial institutions
Fiscal documents in scope
7,436 invoices, 2,567 payment complements, 448 payroll receipts
Bank movements with statement arithmetic verified
4,479
Invoices with a deductibility verdict and its stated reason
3,611
Approved bank-to-invoice links
1,817
Links typed by hand
0

How those 1,817 links were produced — and why that matters more than the total

1,702 were proposed by the deterministic engine. 115 were proposed by the reasoning layer. Zero were entered manually.

That distribution is the design, not an accident. Deterministic logic handles everything where deterministic logic is sufficient, because it is auditable, repeatable, and free. The reasoning layer is reserved for the residue where no rule can decide — an abbreviated counterparty in a bank description, an amount that does not tie to the cent, a payment that has to be allocated across invoices. And every one of those proposals passes through a validation layer with the authority to reject it on identity, direction, amount, date or counterparty before it ever reaches a person.

The number that closes the argument is the last one. Across the entire deployment, no link between a bank movement and an invoice has been typed from scratch. The system proposes; the accountant approves, corrects or rejects. That is the difference between software that assists the work and software that does it.

The deductibility layer shows the same shape: of 3,611 invoices ruled on, 2,902 are deductible, 468 are not, 178 are flagged at risk, and 63 fall outside the rules' scope — every one of them with the specific reason attached, and the at-risk cases telling the firm exactly what is missing while there is still time to get it.

Company metrics screen: movements processed, fiscal documents in scope and approved links.

Illustrative data. No screenshot shows real taxpayer information.

09

What makes it replicable

TimbraBot solves reconciliation. What WASD built to solve it is not specific to reconciliation.

The same architecture applies wherever three conditions hold at once: documents arrive in a format you do not control, the business rules that govern them are hard and non-negotiable, and a wrong answer is expensive. That describes claims intake, supplier onboarding and invoice approval, customs and trade documentation, contract and policy review, regulatory filing, and clinical or legal record processing.

Four components port directly:

The ingestion seam.

Because the engine consumes normalized records and never files, adding a new document origin — another institution, another layout, another delivery channel — is an adapter, not a rebuild.

Reading with arithmetic proof.

A model reads the document; deterministic checks decide whether the reading can be trusted. Anywhere the document carries its own internal consistency — totals, balances, line items against a sum — the same pattern converts an unbounded extraction problem into a bounded one.

Validation with veto authority.

The rule that no model output reaches the database without passing code that can reject it, on criteria specific to the domain, is the reason this system can be trusted with a filing. It is portable to any decision a model proposes.

Expertise entering the engine in plain language.

Every organization has rules that live in someone's head and never make it into the software. Letting the expert write them in their own words and compiling them into executable logic is the component with the widest reach — and the hardest to buy off the shelf.

WASD Studio

Sixteen years building software and digital experiences for corporations in Mexico, among them Alsea, Coca-Cola FEMSA, L'Oréal and Heineken. TimbraBot is our own product; the rest of the work is built on each client's process.

10

How it is built

Technical section. Everything above stands without it.

Models and where they sit.

The system is not tied to a version: every call path is instrumented and every model migration passes through the A/B gate described below. The models in operation are: document extraction runs on Claude Sonnet 5, with Claude Opus 5 as fallback on the retry path; adoption of a fallback result is guarded, so a failed retry can never produce a worse result than the primary read. The reconciliation reasoning layer runs on Claude Sonnet 5. The rules compiler — natural-language rules to executable descriptors — runs on Claude Haiku 4.5, sized to the task.

Invocation.

Direct HTTP calls to the Anthropic Messages API from the orchestration layer's code nodes, which run in a sandbox without package resolution — so the call path, the JSON extraction and the SHA-256 used for content addressing are all implemented inline and unit-tested against a reference implementation. Extended thinking is configured explicitly per call path rather than left to default: disabled on the normal extraction and matching paths, enabled only on the retry path where a failed arithmetic check gives the model a concrete oracle to work against. No tool use in the production paths; the contract is structured JSON, parsed with a balanced-delimiter extractor that survives commentary around the payload.

Prompting and context management.

The reasoning layer is chunked at forty movements per call, with the candidate pool trimmed to invoices that are still open. Inside the prompt, entities are addressed by short positional indices rather than by their database identifiers, with the mapping held server-side and translated back before validation — identifiers never enter or leave the model context. Candidate records were reduced to the six fields the decision actually needs. Together those two changes cut input by 11.5% and output by 60% on measured A/B runs.

Validation as a first-class layer.

No model output is written to the database. Every proposal is re-derived against the candidate set and rejected on any of: unknown identifier, wrong direction, amount outside the tolerance for its match type, date outside range, missing applied amount on a partial, counterparty contradiction, or an invoice already consumed in this or a previous chunk. Lookups in that layer are done on prototype-free maps, so prototype-pollution inputs fail structurally rather than by accident. Rejections are counted by reason, per run, and persisted — the validation layer is instrumented as a product surface, not as debug output.

Cost engineering in production.

Every model call is logged with model, token counts, cache counters and stop reason, including failed calls, and costed per model through database views. Model migrations are gated: a byte-for-byte offline A/B harness replays real production documents through both the incumbent and the candidate, with a double baseline to separate real change from run-to-run variance, and a hard parity gate on the densest known case before deployment. Redundant work is eliminated rather than optimized — extraction is content-addressed, so re-processing an unchanged document costs zero calls. Concurrent runs of the same company are blocked at the database level.

Claude Code in the build.

The codebase was developed with Claude Code end to end — the web application, the four backend pipelines, the SQL migrations, and the test suites. Backend deployment and database migrations are executed programmatically through MCP servers for the orchestration platform and the database, which is what makes a workflow deploy verifiable: the published code is diffed byte-for-byte against source before it goes live, and the authentication on every webhook is confirmed by probe after every deploy.

We build systems that operate where being wrong is expensive.

TimbraBot reads documents nobody standardized, decides with rules that cannot bend, and puts a person in front of every decision that deserves one. It runs in production, on real money, every month.

Talk to us about your process

If you have a process like that — documents you do not control, rules you cannot bend, and no room for a wrong answer — that is the conversation we want to have.

Tell us your training challenge and we’ll get back to you within one business day.

16+ Years50+ CompaniesUnity Award Winner

By submitting you accept our Privacy Policy.

Reply within one business day · No commitment

Prefer a direct conversation?

Message us on WhatsApp