Amortance.

Dental software

A payment plan is only as right as the treatment behind it.

The insurer’s decision lands weeks after the treatment. Treatment is added, swapped or canceled. The balance the practice financed moves — and the payment plan, written against the original treatment, doesn’t move with it.

For platforms whose practices finance treatment themselves: Amortance replays the plan from its first day against the procedures actually delivered, including the months already charged, and returns every figure with the rule behind it. For the platform, that is a calculation layer it doesn’t have to build and maintain. For the practice, it’s an answer it can read out loud to a patient.

What is for sale

An annual license for the calculation layer. It starts with a review.

Who it’s for
Platforms whose practices finance treatment themselves and carry the balance on their own books — not lenders, and not the practices directly.
What you license
The engine that answers what is owed and why, embedded in your product and configured to your contract rules. One integration covers every practice on the platform. What the annual term covers.
How it starts
A review of 20–50 existing plans, which is also where your event catalog gets written down. What the review is.
What it costs
Two figures, in this order: a fixed integration fee, quoted from the catalog before any code is written; then the annual license, per platform and priced by how many active plans you carry. Where each one comes from.
What it computes
Simple interest on the outstanding principal, on a 30/360 or actual/365 basis, accrued interest settled before principal. Precomputed and add-on contracts aren’t covered, and if that’s what your practices write, this isn’t for you.
Off your support queue
The support work that currently answers “how did you get this number” by reconstructing the history by hand in a spreadsheet.
What exists today
The calculation core is public and runnable — the worked example below is computed by it. In build: multi-tenancy, your event catalog, the API.

The events

What moves after the plan is signed.

Each one is an event on the order with a date it takes effect — and each one has more than one defensible reading.

  • Insurance pays above estimateThe patient's share drops. Was the reduction effective on the date of service, or on the day the payment posted? The interest already charged differs either way.
  • Insurance pays below estimateThe patient owes more than they signed for. Does the installment rise, or does the term extend?
  • Treatment added mid-planNew production joins an existing plan. Interest accrues from the day the work was performed, not from the day somebody typed it in.
  • A procedure is canceledMonths of payments have already been collected against work that won’t happen. The refund is one number if you unwind from today, another if you unwind from the start.
  • A discount, write-off or financial assistanceA reduction the practice chooses today that the contract says applies from a date months ago, to a balance that has been accruing since the plan began.

Three of the five are followed to the cent in the worked example below and published as test vectors: an adjudication above estimate, treatment added, a procedure canceled. Insurance below estimate and write-offs are in the catalog and not yet in the published vectors.

Worked example · synthetic data, real mechanics

One plan, three events, and the month it should have closed.

Every event here has two dates: the day it takes effect and the day it reached the system. Wherever a figure below changes, the gap between those two is why.

The plan as signed

March 2026. Treatment of $4,000 to the patient after an estimated $1,260 of coverage, $400 down, the rest financed.

Financed
$3,600.00 over 12 months at 9.90%, 30/360 — every month counted as thirty days, the year as 360
Installment
$316.33 monthly, first on April 1
Interest if nothing changes
$195.97

What happened instead

  • April 23Insurance pays $870 more than estimated. The installment becomes $233.99.
  • May 14Periodontal treatment is added, $238. The installment becomes $258.16.
  • October 5The implant is canceled, $1,785 of treatment that won’t happen.

Replaying changes the months already charged

The first installment was collected on April 1, before anybody knew what insurance would pay. It wasn’t reversed — it was re-split against a principal that had been $2,730.00 all along.

Installment 1 · April 1To interest, $To principal, $Balance after, $
As charged at the time29.70286.633,313.37
Replayed, insurance effective from the date of service22.52293.812,436.19
Difference−7.187.18−877.18

Every figure carries its derivation. The $22.52 above isn’t a number in a cell:

$2,730.00 × 9.90% × 30/360 = 22.5225 → $22.52

Principal of $3,600.00 less $870.00, effective 2026-03-01; thirty days on a 360-day basis; half a cent rounds up. That is what a practice reads back to a patient, and what an auditor asks for eighteen months later.

The patient has paid $2,241.12 — the $400 down and seven installments — against a patient share that came to $1,583.00.

They ask for the difference back: $658.12. That is the arithmetic of someone who doesn’t pay interest, so it isn’t the answer. The ledger has one too. Which of the two is right is a clause in the plan agreement rather than a property of the arithmetic — and this example takes the clause that unwinds a cancellation from the day the plan began.

Unwinding the cancellationInterest charged, $Refund due, $Plan closed
From today, forward only124.81533.31October
From the plan's start, replayed21.89636.23August
Difference102.92102.92two months

Under that clause the $102.92 is interest charged on an implant that was never placed, and the debt reached zero on the fifth installment, in August — the two payments after that weren’t payments at all. Under a contract that unwinds from the cancellation date instead, $533.31 is the correct refund and the ledger is right. What the forward-only ledger can’t do either way is tell you which clause yours is: its balance is zero, its books balance, and the question can’t be put to it.

Check it yourself

The engine is published as engine.py, this example’s complete input as a file, and a second implementation — written from the specification, sharing no code with the engine — recomputes every figure in it. Both are MIT.

curl -O https://amortance.com/dental.json
curl -O https://amortance.com/verify.py
python3 verify.py dental.json

Add --all to print every figure it derived, next to the published one.

Your own book

Four questions to put to your own system.

Nothing above proves anything about you — it’s a synthetic plan with real mechanics. These four you can answer this week, without me and without an export.

Do you get these events at all?
Take one quarter of plans and count the ones touched after signing by an insurer paying above or below estimate, added treatment, a canceled procedure or a write-off. If that count is a handful, stop reading.
Which date do you apply?
Take one plan where insurance paid more than estimated. Find the date your system applied the reduction, then find the date the plan agreement says it takes effect. If those are the same date because somebody chose it, good. If they’re the same date because nobody chose, that’s a different answer.
What happened to the interest already charged?
On that same plan, find an installment collected between the date the agreement says the reduction takes effect and the day the adjudication actually reached your system — two dates that differ even when the pair above didn’t. Was that installment’s split between interest and principal ever revisited? If it wasn’t, the difference is still in the balance, and every installment after it inherits it.
Who answers when the patient asks?
Find the last ticket that asked why a balance was what it was. Count the hands it passed through and the days it took. That is the part of the cost you already pay.

If all four come back clean, you don’t need this layer, and knowing that costs you an afternoon. If the second and third don’t, the review is where a number gets put on it — on your plans rather than on mine.

Or answer it with figures instead of questions. self-check.txt is one short contract — twelve monthly payments, one credit that takes effect three weeks before the money arrives — with both answers side by side. Key it into your own module, compare eight figures, and whichever column you land in is the policy your system applies today. No export, nothing sent to me, and the inputs and both columns are published as self-check.json for your own verifier to re-derive.

What the category documents

Open Dental writes the policy down. The policy is forward only.

Open Dental — whose manual is public down to what each checkbox does, which is why it can be quoted line by line — states it plainly: insurance payments and adjustments applied to the attached production after a plan was created

automatically update future PayPlan Charges if the Amount Override is blank or Permanent Lock is unchecked

and the automatic changes are not tracked in the audit trail. Lock the plan and nothing moves at all; leave it unlocked and what moves is the charges still ahead. Neither reading goes back over the months already charged. (Open Dental manual, Payment Plan.)

The same manual ships a separate report whose only job is to find plans charged for more production than stands behind them, and it’s candid about what fixing one costs. “A negative charge and negative payment plan debit are added for each item selected” — and then, if the patient has been paying, the plan “may become overpaid after fixing the production”, and somebody runs a tool to “re-allocate the income from the overpaid charges” by hand. An offsetting entry and a transfer somebody has to run, which is what you get instead of a recalculation. (Open Dental manual, Payment Plans Overcharged Report.)

Forward-only is a defensible policy, and for many practices it’s the right one. What the manual documents nowhere is the other answer: single charges can be edited, deleted or zeroed by hand, but nothing re-splits the payments already collected against the date the plan agreement points to — not for the patient on the phone, not for the practice's own books.

What stays with you

Everything that touches money or people.

  • The layer moves no money and stores no cards. Your processor keeps doing exactly what it does.
  • I don’t need patient names, contact details or clinical notes — amounts, dates and procedure codes are enough, and whatever you do send is deleted once the review is delivered.
  • I’m not the creditor, and nothing here makes anyone one. Your contracts and disclosures remain yours.
  • I don’t decide who gets a plan. No scoring, no underwriting.

Interface draft · not shipped

What you’d actually be integrating.

The API is in build, so the shapes below are a draft and not a contract — the first partners’ catalogs settle them. What isn’t a draft is the division of labor: you send terms and events, you get back figures with the rule and the arithmetic behind each, and every decision about your books stays on your side.

What your product sends

The plan, its history, and the adjudication that just posted with both of its dates. on is the date the event takes effect and received the date it reached you: the whole product lives in the gap between them. The history has to be there because a replay derives the old answer as well as the new one; the postings and earlier events are abridged below and go in full in a real call. The worked example’s complete input is published as dental.json.

POST /v1/replay
{
  "contract": {
    "accepted": "2026-03-01",
    "amount_financed": "3600.00",
    "apr": "0.0990",
    "day_count": "30/360",
    "installments": {
      "first": "2026-04-01",
      "count": 12,
      "every": "month"
    }
  },
  "history": {
    "events": [],
    "postings": [
      {
        "kind": "installment",
        "no": 1,
        "on": "2026-04-01",
        "amount": "316.33"
      }
    ]
  },
  "event": {
    "kind": "benefit_adjudicated",
    "on": "2026-03-01",
    "received": "2026-04-23",
    "patient_share_change": "-870.00"
  },
  "restate_from": "2026-03-01"
}

What comes back

The answer to the re-split table in the worked example. Not a balance: the postings whose split the event changes, each with what it was, what it becomes, and the rule that moved it.

{
  "restated": [{
    "posting": "installment 1",
    "on": "2026-04-01",
    "amount": "316.33",
    "was": {
      "interest": "29.70",
      "principal": "286.63"
    },
    "now": {
      "interest": "22.52",
      "principal": "293.81"
    },
    "because": {
      "balance": "2,730.00",
      "rate": "9.90%",
      "days": 30,
      "basis": 360,
      "exact": "22.5225",
      "rounded": "22.52"
    }
  }],
  "repriced": {
    "from": "2026-05-01",
    "installment": "233.99"
  }
}

What you do with what comes back is your call — read the derivation to whoever asked, restate the period, or turn the difference into an entry with an effective date and a reason on it. The service never writes to your books and holds no credentials that would let it. It runs either as a service you call, which needs no borrower identity, or as the same engine as a library inside your own deployment, which sends nothing anywhere: identical arithmetic, same test vectors, and which shape gets built first is the first partner’s call rather than an announcement here.

Build or buy

The core is MIT, which makes building it yourself easier, not harder.

So the question isn’t whether your team could write this. It’s what they’d be taking on, and whether it belongs on your roadmap for as long as the platform runs.

What is already written down
Six kinds of post-signature event across two contract types, each replayed under four combinations of day count and policy, plus the short self-check under both readings of one date — 588 published figures that a second implementation re-derives from the specifications alone. That is the regression suite you’d otherwise be writing first.
The combinations, not the events
One event on its own is the case everybody gets right. An adjudication that lands after an installment has already been collected, then treatment added at a date months in the past, then a cancellation that unwinds to the day the plan began — that is where two correct-looking implementations disagree, and it’s what the examples are built around.
Rules change; old answers must not
When a rule changes, the vectors for the old rule stay and the new ones are added beside them, so a figure you gave a customer two years ago still reproduces. Keeping that true is the maintenance: a rule change isn’t a patch but a new set of vectors beside the old, and it doesn’t end while the platform runs.
What you keep either way
The core, its vectors and the event catalog written during the review are yours under MIT whichever way you decide. Nothing here is an argument that you couldn’t leave later, and I’d rather you bought this knowing that.

The case for building is that you’d own it outright, and it’s a real one. Two things sit on the other side. The first is the maintenance above: a standing line on your roadmap, or a line on an invoice. The second is what you’d be building from. Your own engine gets written against your team’s reading of your own contracts, and that reading is the thing the review exists to check; build first and you have shipped it into the calculation before anyone has held it up against the paper. For me this is the whole job, and for you it’s one corner of one.

How it starts

A review of 20–50 existing plans.

You send an export, I replay every plan against the rules your contracts actually state, and you get back a per-plan comparison — including the periods already charged.

Where it leads is the calculation layer embedded in your platform: the review is where the rules of your book get written down. An integration configured from assumptions instead would return confident wrong numbers.

Email me about a review No integration, no credentials, nothing to install
Scope
One contract type and an agreed set of events. An export from you; anonymized is fine.
What you send
Terms, the payments collected, and every change after signing with the date it takes effect. What that has to contain, what it may leave out, and what to do when you don’t store the second date: sample-export.txt, with sample-export.csv as rows.
Delivery
10 business days from the agreed start, once the export and the contract rules are complete.
Result
A comparison for every plan, the reason for each difference, and the actions your team can take. A sample of the shape it arrives in: sample-review.txt and sample-review.csv, on synthetic data.
Your data
The export is processed on my machine, never uploaded anywhere, and deleted once the review is delivered — or sooner, on request.
Reviewer
Evgenii Agafonov, founder. In consumer lending since 2022 as a developer and tech lead; I do the reviews myself.

And then what

From the report to a running integration.

The review can end the conversation, and often should. If it doesn’t, this is the path — and none of it starts before the report is in your hands.

1 · The report
A comparison for every plan in the export, and the rules your book actually follows written down. If your own module agrees on all of them, that’s the result — and the question left is what producing those answers and keeping them right already costs you: the reconstructions by hand, the support time, the developer days every time a contract term moves. If that’s cheap where you sit, there’s nothing further to buy, and I will say so.
2 · The scope
If it doesn’t agree — or it agrees and keeping it that way is the cost — the catalog from the review says which events the layer has to cover, which of your systems calls it and what comes back. That becomes a written scope and a fixed integration fee, quoted from the catalog before any code is written.
3 · Acceptance
The layer ships with its test vectors and with the plans from your own review as cases. Your team runs them through the integration, called from your own product, in the environment you choose. Agreement on every one is the acceptance test — a number you check, not one I assert.
4 · The annual term
Begins at acceptance rather than at signature, and is priced per platform on a band of active plans. What it buys is keeping the calculation, your rules and the test vectors in step with your contracts and your product, for as long as you run it.

Two figures are missing from this page: the integration fee and the annual band. The first follows from the catalog, the second from how many active plans you carry, and I’d rather quote both against your own book than publish a figure I would have to take back. Both are named in writing and fixed before any integration work starts, which is the part that matters more than seeing them here.

Questions

Asked first, every time.

What happens to the integration if Amortance goes away?

The standard procurement question, and a fair one for anything that sits in your core. Two things are already true and cost you nothing: the calculation core and its test vectors are published under MIT, so the arithmetic and the cases that prove it stay yours to run and to check a replacement against; and the event catalog written during the review is your document, not mine. Neither of those keeps a hosted layer running — a license can’t promise that, and I’m not going to read it as if it could. What goes beyond that — how the embedded layer is licensed, which continuity guarantees belong in the contract — is worth settling in writing with your procurement rather than promising on a web page.

Does this change our licensing position?

Nothing here makes anyone a creditor who wasn’t one already: the practice is the creditor before and after, I take no fees from patients, hold no funds and originate nothing. Whether that changes anything in your own licensing is a question for your counsel and your states rather than for a vendor’s FAQ — what I can hand them is a precise description of what the layer touches and what it doesn’t.

Our plans are interest-free, four payments.

Then you don’t need this, and I’ll say so. With no finance charge nothing accrues, so a cancellation is arithmetic rather than a re-split between interest and principal — your own module already does it. A plan of four payments with no finance charge also stays outside Reg Z’s creditor definition, but that’s about which disclosures apply, not about whether the calculation is hard.

What if the review finds nothing?

You get a clean result in writing, with the rules your book is actually following stated explicitly. A review that finds nothing is a good outcome, and for most platforms that is where it ends. The one case where it doesn’t is a book whose answers are right and expensive to keep right — the reconstructions by hand, the support time, the developer days on every plan term that moves. That cost is yours to weigh, and the catalog from the review is what an integration would be quoted from.

We already have payment plans in our product.

Every system in this category does, and the review isn’t a check of whether yours prices an installment correctly — it almost certainly does. The narrower question is what happens after: an adjudication that lands weeks late, treatment added at a date in the past, a procedure canceled. Does the answer still agree with what the plan agreement says, on the months already charged? If it does, you get that in writing.

Files

Ten files, each with one job.

In the order they usually get opened. Nothing needs an account, sits behind a form, or asks you for anything first.

Try it against your own system

self-check.txt
One short financed plan — twelve monthly payments and a credit that takes effect three weeks before the money arrives — with both answers side by side. Key it in, compare eight figures, and whichever column you land in is the policy your system applies today. Twenty minutes, and nothing leaves your building.
self-check.csv
The same eight figures as rows, for a script rather than for the eye.
self-check.json
The inputs and both sets of results in machine form, so your own engineer can re-derive them instead of trusting the table.

See what the review produces

sample-review.txt
A finished review on synthetic data: which figures were checked, which agreed, which didn’t, the contract rule each was checked against, and the action for each difference. The form is real; the plan is invented.
sample-review.csv
The same comparisons as rows, one per check.

See what you would have to send

sample-export.txt
What an export has to contain: the terms, the payments actually collected, every change after the sale with the date it takes effect, and the figures your own system produced. And what it must never contain — names, contact details, card numbers: none of that is needed to check the arithmetic.
sample-export.csv
One plan exported that way, as an example of the shape. Your own columns are fine.

Check the arithmetic itself

engine.py
The calculation core that produced every figure on this page. MIT, runnable, no dependencies, yours to keep whatever you decide.
verify.py
A second implementation that shares no code with the engine and re-derives all 588 published figures from the specifications alone. It is the reason none of the numbers here has to be taken on trust.
dental.json
This page’s example in full: the terms, every posting, and the expected results under four combinations of day count and policy. This is what verify.py reads.

Start here

Two sentences are enough.

What your practices put on a plan, and what changes after the sale most often. Within a day you get back: whether the layer applies to the change you describe, what checking it on your own book would take, and a plain no when it doesn’t apply.

hello@amortance.com No form and no call required. Written by default, so every answer arrives with its figures attached. Or check first whether the data even exists on your side: what an export has to contain.