FAQ

What transaction enrichment is, and why it is hard

This page is about the problem, not the product. If you have never had to read a bank feed, the first three answers are the ones that matter; the rest are the parts people get wrong once they have.

The basics

What is transaction enrichment?

Turning the string a bank sends you into structured data. In goes a narration: SQ *BLUE BOTTLE 8005551234 CA. Out comes a merchant, a category, a channel, a country, a confidence score, and a flag for whether it repeats.

The category matters as much as the name. "Blue Bottle Coffee" is a fact; "Coffee" is what lets an app sum a month of spending, and what lets a lender tell rent from takeaway.

Why is a bank narration unreadable to begin with?

Because nobody wrote it for you. A narration is an operational record passed between systems that already know what it means: an acquirer's terminal reference, a switch's session id, a biller's customer number. The merchant name is in there by convention, not by design, and it shares the string with everything else.

So the same coffee shop arrives as SQ *BLUE BOTTLE, as TST* BLUE BOTTLE 04, and as BLUEBOTTLECOFFEE.COM. None of them is wrong. They are three systems each abbreviating for their own reasons.

Can't you just look the merchant up?

That IS the job, and the lookup table is the hard part. It is called a directory: canonical names, every alias each one arrives under, the category, the MCC. Kederi's holds 662 entries across 9 markets.

Building one is slow, and keeping it current is slower. Names change, billers appear, and the same entity arrives spelled a different way on every feed it touches. That maintenance is most of the work, and it is why a directory is a product rather than a file.

Why African feeds are a different problem

This is the part that does not carry over from enriching card descriptors, and it is not a matter of adding more merchants.

What actually differs?

A US card feed names a business: one accepted a card, and it is in the string. A Nigerian bank feed usually does not. Money moves between people, over a rail with a name of its own, carrying whatever the sender typed into a reference box.

Read NIP/GTB/ADEBAYO CHUKWU/rent for august as a merchant descriptor and you get nothing. Read it as a transfer and you get a counterparty, an intent, and a period marker that is not part of anyone's name.

The failure to watch for is a quiet one. Fed a transfer narration, a matcher built for card descriptors will often find something: the bank, the payment rail, the switch. It returns a confident answer naming the wrong party, and a recognisable name is worse than no name, because nothing downstream doubts it.

In Nigeria the hard case is a transfer between two people. The rail is named, the sender typed the reference, and no business appears in the string at all.

Is this just Nigeria?

No. Nine markets are covered, and not all to the same depth: each one is marked full or beta on the coverage page, which says what separates them. They are labelled individually rather than averaged into one number, because an average would hide exactly the thing you need to know.

Nigeria is the one market marked full. Its formats and its directory are checked against real Nigerian bank statements.

Words that mean less than they sound like

What does "recurring" actually mean?

That a charge is part of a series: the same counterparty, at a roughly steady interval, for a roughly steady amount. It is a property of several transactions, never of one, so no amount of reading a single narration can tell you.

Which makes the hard part identity, not arithmetic. If a merchant resolves as "Netflix" eleven times and "NETFLIX.COM 8445050" once, the series splits and the subscription disappears. That is why the recurring benchmark scores whole series end to end from raw strings, rather than scoring the cadence detector on clean input it would never see. We do not quote that score here, for the reason below.

And "confidence"?

How far the answer got down the ladder, not a probability. A dictionary hit on an exact alias scores high; a fuzzy match on a mangled string scores low and carries low: true. Every result also carries a trace saying which stage decided it.

Treat it as a routing signal: below your threshold, send it to review rather than to a customer. A number that cannot be acted on is decoration.

What is a good accuracy number?

Ask what it is measured over before you compare any two, and ask for the second number. Recall and precision fail independently: a matcher that guesses generously scores beautifully on a labelled set and invents merchants on everything else, and a labelled set structurally cannot see that.

So Kederi keeps both. The public set is 234 narrations scored on every field at once; a separate precision set of 106 narrations must resolve to no merchant. The second is the one worth watching: it is the half a labelled set structurally cannot see.

We do not publish the percentages, and that is deliberate. Both sets score narrations from banks we know well, and a score there says little about a bank we have not seen.

Ask us and you get the scores and the method, in writing. The only number that settles anything is the one you get from your own narrations, and the playground runs on the real engine so you can produce it yourself.

What enrichment does not do

Worth being explicit, because the category is often sold as though it does all of this.

It does not score anyone

Enrichment describes transactions. It does not decide whether somebody is creditworthy, and a clean category feed is an input to that judgement rather than a substitute for making it.

It does not detect fraud

A duplicate charge and a surprising amount are both flagged, and both are anomalies in your own spending. Neither is a fraud model, and calling them one would be the kind of claim this site exists to avoid.

It does not move money

Kederi reads strings. It holds no account, initiates no payment, and needs no rail access to do its job.

It does not guess to fill a gap

An unresolved narration comes back unresolved. A plausible brand name the engine cannot justify is worse than an empty field, because an empty field is visibly a gap and a wrong name is not.

Practical questions

Why not just use a language model?

Partly cost and latency: enrichment runs on every transaction of every account, so the unit has to be cheap and fast.

Mostly, though, because the failure mode is wrong. A model asked to name a merchant will name one, and it will be fluent about it. A directory either holds the entry or it does not, and "it does not" is an answer you can route on. The engine is built to be the part that knows rather than the part that improvises.

What data do you need, and what do you keep?

A narration is enough for a single enrichment. We keep none of it: the answer is computed and returned, and nothing is written down.

Recurring detection, duplicate detection and a cash-flow forecast need history, so POST /reports takes the transactions in the request and hands back the report. You keep the rows; we compute over them and forget them. The console works the same way: your statement is parsed in your browser and stays there.

A narration often names somebody who is not you: the person you paid, because that is what the sending bank wrote. They never agreed to anything. Not holding it is a stronger promise than any policy about how long we would have.

What happens when it gets something wrong?

You tell it. POST /feedback takes the narration and what the answer should have been; corrections are reviewed, folded into the directory, and added to the benchmark so the same mistake cannot come back unnoticed.

You can see what became of it. GET /feedback shows your own corrections with a status, and a declined one comes back with a reason rather than silence.

Still deciding

The fastest answer is your own narrations

Anything above is a claim about our data. Paste a descriptor into the playground and you have an answer about yours, in a few seconds and without an account.

If a narration comes back wrong, that is worth telling us: corrections feed the directory and there is an endpoint for them.