Build vs Buy: What Sanctions Screening In-House Really Costs
Building sanctions screening in-house takes 24 to 37 engineer-months plus 13 to 20 engineer-days a month to maintain. Measured against our own build.

Building sanctions screening in-house is a 24 to 37 engineer-month project before it screens a single customer. After that it costs somewhere between two-thirds of a full-time engineer and a whole one, permanently. We know the shape of that bill because we paid it. This post itemises the work from our own build, as of August 2026.
This is written for the engineering leader who has been handed "add sanctions screening" and is deciding whether to build it. It is not an argument that building is always wrong. There is a section below on when building is the right call.
What does building sanctions screening in-house actually involve?
Ten distinct components, of which name matching is only one. Most teams scope the matching engine, ship it, and then discover that matching was the small part. The table below is the full scope of work, with what each piece cost us and what it costs every month afterwards.
| Component | What it covers | Build (engineer-months) | Ongoing (engineer-days/month) |
|---|---|---|---|
| List ingestion and source onboarding | Fetching, parsing, and mapping each publisher's format into one schema | 6–9 | 3–5 |
| Delta detection and change history | Knowing exactly what changed between runs, and when | 1–2 | 0.5 |
| Name normalization and transliteration | Unicode handling, diacritics, script coverage, country and demonym maps, phonetic encoding | 3–4 | 1–2 |
| Matching and recall | Index design, candidate generation, per-channel retrieval | 3–4 | 1 |
| Index sync and search infrastructure | Keeping the search index consistent with the system of record, and running it | 2–4 | 2–3 |
| Scoring and thresholds | Turning candidates into ranked matches, tuned per list and entity type | 2–3 | 1–2 |
| Monitoring re-screens | Re-screening the whole book every time a list changes, at volume | 2–3 | 1 |
| Case management and reviewer workflow | Queues, assignment, decisions, justification, escalation | 3–4 | 1 |
| Audit trail and retention | Append-only decision records, list-version pinning, reconstruction | 1–2 | 0.5 |
| Uptime, on-call, and backfills | Running the whole thing as production infrastructure | 1–2 | 2–4 |
| Total | 24–37 | 13–20 |
How these numbers were derived. They come from our own build rather than from a survey. Each row is what that component actually cost us to get into production, expressed as the effort a competent engineer would need to reproduce it. Prototypes and abandoned attempts are not counted, which makes these figures conservative. The conversion assumes an engineer who already understands the domain. Add 30 to 50 percent if the team is learning sanctions data on the job.
The ongoing column is the one that decides build versus buy. Thirteen to twenty engineer-days a month is not a maintenance rounding error. It is most of a permanent headcount, and it does not end when the project ships.
Why is list ingestion the largest line item?
Because sanctions lists are not a data feed. They are dozens of separate publications, each in whatever format its issuing government happened to choose, with no shared schema and no versioning contract.
Here is the actual format distribution across every list source configured in our ingestion service, as of August 2026. Shares are rounded:
| Source format | Share of sources | What it costs you |
|---|---|---|
| HTML page | ~45% | Scraped. No schema, no version, and no notice when the layout changes |
| JSON | ~21% | Usually the easiest, but field names and nesting differ per issuer |
| XML | ~13% | Structured, but issuer-specific and occasionally self-inconsistent |
| PDF via headless browser | ~6% | Requires a real browser inside your pipeline |
| Zipped XML | ~4% | Archive handling on top of XML parsing |
| XLSX | ~4% | Spreadsheet parsing, merged cells, header drift |
| PDF, ODS, and vendor API | ~6% combined | Layout-dependent text extraction, a second spreadsheet format, and an authenticated paginated feed |
Nine distinct formats, and no publisher coordinates with any other.
The number that should worry you is the first row. Close to half of all sanctions sources are published as web pages with no machine-readable alternative, so the only way to consume them is to scrape a layout that can change without notice. That is a property of how sanctions authorities publish, not of any one vendor's coverage. Anyone ingesting these sources inherits it, including whichever vendor you buy from.
The second surprise is that a generic parser does not work. Roughly four out of five processor modules in our ingestion service are source-specific rather than generic format handlers. Each list needs its own field mapping, its own date-format quirks, and its own rules for how one record relates to aliases, addresses, and identity documents.
We did not plan it that way. The service began with a single shared processor covering OFAC's SDN and non-SDN lists. One day after the second list was added, we replaced the shared processor with per-list processors. That architecture held for a while, and then we rewrote the ingestion layer a second time, moving field mappings out of code and into declarative configuration so that onboarding a new list stopped requiring a deploy.
If you build this, budget for rewriting your ingestion layer twice. We did, and we were expecting the problem.
What breaks first in a home-grown screening system?
Five things, in a consistent order. Each one fails quietly, which is what makes them dangerous.
1. Source ingestion
An issuing body changes a page layout, a column header, or a date format. Your scraper still returns 200 OK and still writes rows. It just writes fewer of them, or writes them into the wrong fields. Nothing alerts, because nothing errored.
The defence is per-source record-count and shape assertions on every run, plus alerting on unexpected deltas. That is work nobody scopes at the start. It is also why the ingestion line in the table above carries the heaviest ongoing cost.
2. Normalization coverage
This is the failure mode we would most want a team to hear about, because it is invisible from inside the code.
Our shared normalization library has moved through six schema versions. One release added around 160 entries to the country map after we found that three already-live sources reported nationality in their own language rather than in English or ISO codes. France's list published country names in French. The Netherlands published Dutch demonym adjectives. Germany published German ones. None of it was mapped.
Nothing crashed. Nothing logged an error. Those records simply carried no usable country signal, which quietly weakened every match involving them. We found it by auditing normalized output source by source, not from monitoring.
An earlier release fixed the opposite problem. One shared nationality was being counted through two separate channels, so a single weak signal inflated a match score twice over. That produced false positives rather than false negatives, and it was equally invisible until someone went looking.
Both are ordinary bugs. The point is that neither announced itself, and both changed screening outcomes directly.
3. The search index
Screening does not query your primary database directly. It queries a search index built from it, and that index has to stay consistent with the system of record. Two ways that goes wrong, both silent.
The first is drift. The pipeline propagating changes into the index stalls, and the index carries on serving yesterday's data. Every query succeeds. They are simply being answered from a stale corpus. A newly designated party is absent from results for as long as the stall lasts, and nothing on the query path indicates it.
The second is a configuration trap worth stating plainly, because the default is wrong for this use case. A search index configured with a memory-eviction policy will silently discard records when it approaches its memory ceiling. That is correct behaviour for a cache and a compliance defect for a sanctions index. The records evicted first are the least recently matched, which is exactly the population whose disappearance nobody notices. The query still returns successfully. It just returns fewer hits, so a designated party that was evicted now screens clean. That is a false negative produced by a memory setting.
The index must be configured to reject writes when it runs out of room rather than evict, so that hitting the ceiling is a loud, detectable outage instead of a quiet correctness failure. It also needs an independent reconciliation check that counts records on both sides, because a sync pipeline reporting its own health will report healthy throughout both of these failures.
4. Monitoring at scale
Screening one name is easy. Re-screening your entire customer book every time any list changes is a different engineering problem. It needs worker pools, backpressure, resumability, and deduplication so that one list update does not raise the same alert several times.
The naive version, a nightly job that re-screens everyone, works until the book reaches a size where the job no longer finishes inside the night.
5. Audit defensibility
The last thing to break, and the most expensive to fix, because it cannot be backfilled. FATF Recommendation 11 requires records sufficient to permit reconstruction of individual transactions, kept for at least five years [FATF Recommendations, R.11].
Applied to screening, that means answering this two years later: why was this specific customer cleared, which version of which list was in force at the time, what thresholds were configured, and who decided. Most home-grown systems log the decision. Very few log the configuration and the list version that produced it. If you did not capture it at the time, no amount of later engineering recovers it.
Is OpenSanctions the free middle path?
Partly, and it is a genuinely good option for the right team. It deserves an honest assessment rather than a dismissal.
OpenSanctions publishes consolidated sanctions and PEP data, and its matching API, yente, is open source under the MIT license [yente documentation]. You can self-host both. That removes the ingestion component from the table above, which is the single largest line item.
Three things to weigh before treating it as free:
The data license is non-commercial by default. The bulk data is published under Creative Commons Attribution NonCommercial (CC BY-NC) [OpenSanctions licensing]. Commercial use requires a paid arrangement. The hosted API publishes a rate of €0.10 per query [OpenSanctions API]. The flat-rate screening license for internal commercial use is quote-only, so you cannot budget it from the website.
Self-hosting is infrastructure, not a library. yente requires a running Elasticsearch or OpenSearch instance, and its Python dependencies include components that may need a local build environment [yente repository]. You now own a search cluster, its capacity planning, its upgrades, and its on-call rota.
It solves ingestion and matching, not the compliance layer. The last four rows of the table above (monitoring re-screens, case management, audit trail, and operations) are still yours to build, and so is most of the index-sync row, since you are now running that cluster. That is 7 to 11 engineer-months before the index work, and the majority of the ongoing load.
The honest summary: OpenSanctions plus yente is an excellent choice for a technical team that already runs search infrastructure, needs matching, and intends to build its own workflow layer. It is not a shortcut past the compliance platform.
When does building in-house actually make sense?
Four situations, and they are more common than vendors admit.
Unusual matching requirements. If you screen something commercial vendors do not model well, such as vessels with non-standard identifiers, goods classifications, or an internal watchlist with its own semantics, a bought product may not be extensible enough. Verify this by testing against a trial, not by assuming.
A hard data-residency mandate. If regulation or a customer contract forbids sending names to a third party, and no vendor offers deployment in your jurisdiction, the decision is already made.
Existing data-engineering capability. If you already run ingestion pipelines, own a search cluster, and have an on-call rota, the marginal cost of adding sanctions data is far below the numbers in the table above. The ongoing load lands on a team that exists anyway.
Extreme volume. Per-entity vendor pricing stops being attractive at some scale. Work out where your own crossover point sits rather than assuming one. Our published rates run down to $0.32 per entity per month on annual billing, and per-check vendors go lower still, so the crossover is usually much further out than teams expect. See how much sanctions screening costs for the full pricing-model comparison.
When it does not make sense: when the driver is cost alone. A 24 to 37 engineer-month build plus 13 to 20 engineer-days a month of maintenance is a large number to set against a subscription. Run that arithmetic against your loaded engineering cost before starting, not after.
What does the maintenance actually consist of?
The ongoing column is not idle time. In our experience it is four recurring categories of work.
Source repair. Publishers change formats without notice. This is the largest recurring cost and it never trends to zero, because it scales with the number of sources you cover.
Normalization expansion. Every new source arrives with its own conventions: a new date format, a new way of writing nationality, a new transliteration convention. Our country and demonym map has been extended repeatedly, and each expansion was triggered by real data rather than by planning.
Re-indexing after normalization changes. This one is easy to miss. When the way you tokenize names changes, every previously indexed record is still encoded under the old scheme. Our normalization schema has moved through six versions. Each change requires re-processing the entire corpus so that indexed records and incoming queries are computed the same way. If the two sides drift apart, recall degrades silently. Nothing errors.
List onboarding. New sanctions programmes and new jurisdictions appear continually. Each one is a new processor, a new field mapping, and a new set of quirks. See why fuzzy name matching is harder than it looks for what the normalization side of this involves in practice.
Frequently asked questions
Can I just download the OFAC SDN list and match names myself? You can, and it will work for a demo. It stops working at the point where you need more than one list, because the second list will arrive in a different format with different fields. It also does nothing for monitoring, case management, or audit reconstruction, which are what a regulator asks about.
How long does it take to build sanctions screening in-house? Our estimate is 24 to 37 engineer-months for a full screening and compliance platform, derived from our own build. A matching engine over a single list is far less, perhaps two to three engineer-months. The gap between those two numbers is the entire point of this post.
Is OpenSanctions free for commercial use?
No. The bulk data is published under CC BY-NC, which excludes commercial use, and commercial arrangements are priced separately. The hosted API publishes €0.10 per query. The yente software itself is MIT-licensed and genuinely free, but the data license and the infrastructure to run it are not.
What is the hardest part of building screening in-house? Not the matching algorithm. It is keeping ingestion working across dozens of publishers who change their formats without notice, and keeping normalization consistent between indexed records and live queries. Both fail silently, which means you find out from an examiner rather than from an alert.
Does building in-house make us more compliant? No. Where the screening runs has no bearing on whether your programme is adequate. Screening supports a compliance programme; it does not constitute one, whether you build it or buy it.
Citations
- FATF, The FATF Recommendations, Recommendation 11 (record-keeping, at least five years, sufficient to permit reconstruction of individual transactions), https://www.fatf-gafi.org/en/publications/Fatfrecommendations/Fatf-recommendations.html
- OpenSanctions, Licensing, bulk data under Creative Commons Attribution NonCommercial (CC BY-NC); commercial and reseller licenses quote-only, verified 19 August 2026, https://www.opensanctions.org/licensing/
- OpenSanctions, Screening API, €0.10 per query, 30-day trial on business-email signup, verified 19 August 2026, https://www.opensanctions.org/api/
- yente, Documentation, MIT license, requires a running Elasticsearch or OpenSearch instance, verified 19 August 2026, https://yente.followthemoney.tech/
- yente, Source repository, self-hosting requirements and Python build dependencies, verified 19 August 2026, https://github.com/opensanctions/yente
- DeRisk Hub pricing, https://deriskhub.com/pricing
- DeRisk Hub platform, https://deriskhub.com/platform
All engineer-month figures and source-format shares in this post are our own, current as of August 2026. They describe what this build cost us and are offered as a reference point, not as a universal estimate. Source-format shares move as new lists are onboarded. Third-party licensing terms and prices change; confirm current terms directly before relying on any figure here.
If you are running this comparison seriously, the useful next step is to price both sides against your own numbers rather than against a generic estimate. DeRisk Hub is the bought side of this decision: sanctions and PEP screening, ingestion across dozens of list sources with most refreshing every five minutes, case management, reviewer workflow, and an audit trail written on every state change, all on published per-entity pricing. It exists because we did the build, and the maintenance is now our problem rather than yours. See what a screening platform costs, compare the alternatives honestly, or start your free trial. Start at DeRiskHub.com.