Your match rules, tested by someone outside your team.
The audit pack: twelve files, a receipt per decision, and a report that opens offline.
On 317 live GB companies recorded 2026-08-29, across seven shapes of input from a company number down to a name cut off by a field limit. Where the evidence would not carry a match, the row came back unresolved and named the column that would have settled it.
Zero wrong bindings were observed across the historical six-shape recording and the separate domain fixture. Observed correctness was 1.0000 on accepted outcomes; this does not guarantee an error-free new workload. Historical results on an internal UK fixture with a preloaded catalogue. The September 5 replay has no full-prompt-verified calls and cannot establish current calibration. No independent customer holdout or global coverage claim.
- Files in the bundle, or fewer, with every absent ledger named and reasoned
- 12
- Totals checked before a pack may ship; the two pair ledgers are checked against seven more
- 10
- Rows written back to your CRM, ERP, MDM or warehouse
- 0
- Re-fetchable receipt per decision the pack cites, refusals included
- 1
Properties of the artefact, checkable by opening it: the file list, the identities in manifest.json, the receipts re-fetched against the live API. These four are not accuracy figures. Every published rate, with its denominator, interval and fixture, is on the accuracy page, and every fixture behind those rates is one we built, not an external customer holdout.
The Sprint is delivered by hand today: we run the five steps on your file, through the same API a key from your Entity Bind workspace calls. The API it runs on is public.
On this page
Three moments, and what you hold after each one
A Sprint is a scoped piece of work at a fixed price. It starts with one file of yours and ends with a directory of files you keep.
- 01
Before anyone quotes — the composition table
We read your file with the preview and send back what is in it: how many rows carry a registration number, how many a domain, how many a postcode, how many nothing but a name, and which of your populated columns we could not use. That conversation costs nothing and commits nothing. - 02
The run — your copy, through the public API
We agree the depth and the window, and your file goes through the same public batch endpoint your own integration would call. Every row is decided once, and nothing is written to anything of yours at any point. - 03
The pack — a directory you keep
Twelve files, assembled from the logs the finished run already wrote, so nothing in the pack is re-decided. If the totals do not reconcile it is not delivered, and you get a failed run and an explanation instead of a bundle you cannot check.
The composition table is the first thing back, before anybody quotes. It costs nothing, and it is the whole of the conversation until you decide whether to run a Sprint.
Start freeTwelve files, and what each one is for
Three are one row per row of your file. Two are one row per pair of your rows the cascade compared. One is one row per company those rows landed on. Row grain and pair grain add up separately, each against its own totals, because a pair is two rows compared, not a row decided.
accepted_matches.csvrowRows the cascade bound to a company that already existed.
The decision column is always match; a first mint can never appear here, because a match and a mint are different claims about your row. Every accepted confidence in a complete pack sits at or above the 0.90 floor, and the pack re-checks that; it does not simply assert it. It is an acceptance contract, not calibration evidence: it says what we refuse to return, not how often an accepted match is right. Read it with the coverage it costs.
new_entities.csvrowRows where nothing matched, so a new company record was created.
Nothing in the catalogue or the registers supported a match. A mint is not a match: these are not calibrated candidate matches and are not subject to the confidence floor, which is why they get a file of their own.
unresolved.csvrowEverything the run did not decide, and the column that would have settled each row.
Including the rows the loader refused to submit at all, absorbed from the skipped log as disposition=not_submitted. Every row carries a typed reason_code, a prescription from a closed vocabulary of eight, and prescription_fields — the cheapest of your own columns that would have made the row resolvable. It names a field, never a predicted outcome.
golden_records.csventityWhich of your rows landed on which company, and the values that survived.
A membership projection, and it deliberately carries no per-field __licence, __permits_resale, __source or __source_as_of columns: a subset of citations is exactly the partial attribution an open register’s licence forbids. Those columns live in the separate export, golden.csv, handed over beside the pack. Do not treat the pack’s golden records as licence-bearing or redistributable.
withheld_matches.csvpairPairs of your own rows the cascade compared and would not call.
No verdict either way. Read settled_by_cluster before you read the count: false is the pair that went to the review queue and stayed on two separate companies; true is the pair the direct comparison could not call and the fold then merged through other edges, so both rows appear on one company in golden_records.csv. Only the first kind is a merge we declined to make, and the two files are what a data buyer cross-checks first.
do_not_merge.csvpairMerges the cascade reached and the guard refused, naming the field of yours that disagreed.
The mapping is total and fixed. A decisive identifier conflict — an LEI, a DUNS or a registration number disagreeing — is cannot_link. Corroborated weak evidence is advisory and never promotes, because two individually rebuttable signals contradicting at once is not a proven incompatibility. The conflict attributes are translated into your CSV’s field names, not our internal fact keys, so which column disagreed and which column would help are answered in one vocabulary.
reversal_map.csvactionWhich rows were grouped onto which company, so you can undo a grouping in your own system.
It is deliberately not a rollback manifest. The v1 contract requires every action to carry a before value, a precondition and a complete inverse operation, and a pack receives no prior state of your systems, so it cannot promise an inverse for an action it never saw. It is a map, not a guarantee.
decision_receipts.ndjsondecisionOne receipt per decision the pack cites, re-fetchable at a URL.
The verbatim decision body, re-fetchable at GET /v1/decisions/{id}. A blocked pair gets a durable receipt too, so a refusal redeems at a URL the same way a merge does. The receipts are not RFC 8785 canonical and carry no receipt_sha256: re-verification is by re-fetching and comparing, not by signature.
ATTRIBUTION.txtbundleThe attribution notice the open registers require.
Companies House is licensed under OGL v3.0 and INSEE SIRENE under Licence Ouverte 2.0, and both require attribution. The notice is written after the CSV and derived from that file’s own __source columns, so it names only the sources the delivered file actually contains, not every register we hold.
report.htmlbundleThe written report: self-contained, offline, and printable to PDF.
A white-label slot can carry your company’s name, set when the pack is assembled; left empty, it renders an unbranded page. The ledger coverage notes sit beside the table, not in a footnote; the cover prints the decision depth that actually applied; and the divergences from the v1 contract are a visible block, not an appendix. A report is never the system of record.
manifest.jsonbundleEvery total in the pack, and the list of what this version does not do.
not_audit_pack_v1: true with an enumerated divergence list, the absent and partial ledgers with their reasons, the four pair counters, the requested and the permitted run depth, the prescription counts, the reconciliation identities and any anomalies. Every number quoted anywhere else in the pack is read out of this file.
manifest.sha256bundleWritten last. Its presence is what says the pack finished.
The pack is assembled in a staging directory and promoted with this file moved last, so a promotion interrupted half-way leaves a directory with no completion marker, never one claiming to be finished. Stale files from a previous run are cleared first, so last run’s do_not_merge.csv cannot survive beside a manifest declaring that ledger absent. The checksums bind the parts of one pack and detect corruption; they do not prove publisher identity.
Why a ledger can be missing, and when a zero is allowed to stand
An absent ledger is absent, not empty
A ledger this version cannot produce is left out and named in the manifest with a reason. It is never shipped as a header with no rows. The incumbent-contradictions ledger is always absent: no incumbent proposal can be supplied yet. The two pair ledgers go the same way when a run had no pairs log. A header-only file reads as we found no dangerous merges, which is a different claim from we did not run the check.
A zero needs a denominator behind it
A pair ledger with no rows is written only when a pairs log exists, because the log carries the denominator. With it, zero means we compared pairs inside your file and refused none — a finding. Without it there is nothing behind the zero, so the file is not written, and the report says beside the table which of the two you are looking at.
Four things worth knowing before you open one of these in Excel
- pack_format
- A format marker naming the pack version, v0, is column 1 of every row of every CSV, not just the manifest. Customers open one file in Excel and forward it, and a self-identifying row is the only version marker that survives that.
- formula escaping
- Cells that begin with a formula character are escaped with a leading apostrophe in exactly five columns — legal name, trading names, entity legal name, phone and registered address — and in no others. The common case is not an attack, it is a UK phone number rendering as #NAME? in the column sold as the surviving golden value. Excel does not display the apostrophe. It is deliberately not applied to your source reference, which is your own key back into your CRM and must round-trip byte for byte.
- run_depth
- The manifest carries both the depth requested and the depth the plan permitted, and the report cover prints the shallower of the two. They can differ silently: a plan clamps a request downwards, and the free plan caps at the probabilistic stage. A Sprint mandated at adjudication that executed entirely at probabilistic would otherwise show up only as a pack full of raise-the-depth prescriptions that could not name the cap.
- prescriptions
- The closed vocabulary is eight values, and three of them name no column of yours on purpose: raise the depth, retry when the escalation stage is available, and none supportable. A cause of ours leads and does not silence the column. A retrieval column your file already carries and normalisation rejected is never prescribed — absent and unusable are different findings, and that row is told to repair the value, not to buy the column.
Every file above is one you keep, and we will tell you which of these ledgers your data will actually populate before a Sprint is scoped.
Start freeInspect an offline audit-pack tutorial
Download an audit pack generated by the product's real import, export and pack builders over eight fictional records in a disposable local test service. It shows how the files fit together before you run your own company file.
This is an educational fixture, not a customer delivery or a production run. Registry sources and research were disabled, the adjudicator was a stub and the requested depth was probabilistic. Supplied identities were not externally verified. Receipts belong to the discarded local service and cannot be re-fetched from the public API. The pack's manifest names its v0 limits; its checksums detect file changes and are not signatures.
- TUT-1 creates a local Tutorial Acme identity. TUT-2 and TUT-3 are accepted into that identity using their agreeing supplied registration number. Follow their source references across new_entities.csv, accepted_matches.csv and golden_records.csv.
- TUT-4 creates the second local identity, Tutorial Northbank. TUT-5 matches it. A newly created local identity is not a calibrated candidate match, which is why the two outcomes have separate ledgers.
- TUT-6 remains unresolved. TUT-7 and TUT-8 have no identifying business data and were not submitted by the loader. All three appear in unresolved.csv, so the eight-row input still reconciles.
A fixed price, read off your own file
This is how a Sprint gets scoped. We run the preview, read the table with you, and price from what is in the file rather than from a row count. If your file is mostly bare names with no town, no postcode and no domain, the table says so before anybody has spent anything — and that is a conversation about which column to add, not a quote.
Underneath the Sprint is the metered product, and its whole price rule is five lines. Three of them are zero.
- 1
- A decision reached by the deterministic or probabilistic stage. CPU only, no egress.
- 5
- A decision that needed LLM adjudication or agentic research.
- 0
- An abstention, at any stage, including the ones that spent the most getting there.
- 0
- A byte-identical replay. Per tenant, per exact content, and with no expiry.
- 0
- An input that normalised to no identifying signal. There was nothing to answer.
Paid monthly subscriptions are purchased through Stripe in workspace Billing. Free needs no card. Usage is recorded separately and does not create automatic usage or overage charges.
The four plans, their allowances and the licence terms →
Send the file you want priced. You get a Sprint scope and a fixed number back, or a straight answer that your file does not need one.
Talk to usBuilt for a master you have to defend
If you already run a licensed company-data feed or a master data management platform, run Entity Bind alongside it. It is the independent decision and evidence layer: it tests, repairs and governs the matches those systems propose, and it will contradict them with a citation when they are wrong.
Teams with a company master they have to defend — an MDM or stewardship queue, a CRM after a migration or a merger, a supplier file before a payment run, or counterparty onboarding that has to survive an audit.
Buying breadth as the product: we hold a fraction of an incumbent’s records and are not building a global company graph. Entity Bind resolves companies and organisations only. It does not resolve people, vessels, bank-account ownership, sanctions exposure, emissions or geolocation, and its results may not be used for eligibility decisions about individuals, including employment, credit and tenancy.
We publish what we do not claim — no SOC 2, no external validation of the accuracy figures, no customer holdout — beside the retention, erasure and tenancy detail a security review asks for. Read the trust page →
A pack whose totals do not reconcile is never delivered
Ten totals are checked at the end of a run and recorded in the manifest, and the two pair ledgers are checked against seven more of their own. Any failure sets the run status to failed, quarantines the action files, suppresses the completion marker and exits non-zero. The artefact never comes into existence.
The load-bearing one is the partition. Every input row lands in exactly one ledger — accepted matches plus new entities plus unresolved equals rows in — and the count of distinct source references equals the number of rows read. That is the first thing a data buyer checks.
Row grain — the ten keys the manifest publishes
- rows_in_partition
- Every input row lands in exactly one ledger. Accepted matches plus new entities plus unresolved equals rows in. This is the load-bearing one.
- distinct_source_refs
- The number of distinct source references equals the number of rows read. A file whose row count cannot be reconciled is the first thing a data buyer checks.
- rows_in_covers_source
- Every row of the source file is accounted for, not merely every row the run happened to see.
- not_submitted_matches_skipped_log
- The not-submitted rows in unresolved.csv are exactly the rows the import's skipped log recorded. A row cannot quietly disappear between the two.
- golden_records_cover_targets
- Every entity any accepted match or mint points at has a golden record in the projection.
- golden_members_sum
- The membership counts across golden records add up to the rows that reached an entity.
- reversal_actions_match_clusters
- The reversal map describes the same groupings the golden records do, action for cluster.
- receipts_cover_decision_ids
- Every decision id cited anywhere in the pack has its receipt in the bundle. Null on an offline pack, because the check did not run.
- state_rows_done_matches
- The resume file agrees with the ledgers about how much of the file was processed. Null when no state file was supplied.
- every_unresolved_row_has_a_prescription
- No unresolved row ships with an empty prescription cell. `none_supportable` is a first-class answer; a blank is not.
Pair grain — seven more, checked separately
- pair_partition
- The pair ledgers add up against the pairs log, and never against the row count. Folding one into the other would double-count your file.
- withheld_pair_ids_distinct
- No pair appears twice in the withheld ledger, including after a resumed import re-appended it.
- do_not_merge_pair_ids_distinct
- The same, for the blocked ledger.
- pair_refs_resolve
- Both sides of every pair row resolve to rows that exist in your file.
- no_cannot_link_inside_one_entity
- This one fails the pack outright. A pack that says we prevented this merge while its own golden records show the two rows collapsed onto one company is self-contradictory and must never ship.
- pair_receipts_cover_decision_ids
- Every pair decision cited has its receipt. Null on an offline pack.
- pairs_complete
- The list was not truncated and pair capture was reported. False makes the run partial and puts a banner on the report, because a claim about every dangerous merge cannot be made over a list that was cut.
A check that did not run is recorded as null, never as true
The state identity is null when no resume file was supplied, and the receipt identities are null on an offline pack — one built without receipts, with no key and no call. Such a pack can therefore never be complete: it reports confidence_verified: false and a status of partial, because a receipt is the re-verifiable artefact and our own refs log is not.
The gate is tested from the failing side too: a deliberately corrupted state file was rejected with exit 1, the action files quarantined, the marker suppressed.
We do not promise a number of dangerous merges prevented
We do not promise a count of dangerous merges prevented, and you should distrust anyone who does before they have seen your file. Zero is a normal, honest result: two rows carrying contradicting company numbers are refused outright and never reach the guard that would have counted them.
A blocked pair needs the cascade to have proposed a merge first. Two rows carrying contradicting company numbers are answered as a non-match outright and never reach the guard that would have counted them, so the blocked ledger can be empty on a file that was full of conflicts.
It carries the guard’s edge pass only. The clustering step runs the same guard again, per member against the cluster as it stands, and a refusal there detaches the member without writing a pair row. The manifest names that gap rather than leaving you a zero you cannot interpret.
It means unreported, never no pairs found, and it fails closed: the completeness identity goes false, the run becomes partial, and the report carries a banner. A claim about every dangerous merge cannot be made over a list that was cut.
If a clean file is the outcome, that is what the report will say — and if the preview sees it first, you get that answer instead of a quote.
Start freeThis is v0, and its manifest lists everything v1 asks for that it does not do
Audit Pack v1 is a frozen contract — version 1.0.0, dated 20 August 2026, and marked in its own header as not implemented. It fixes a boundary; it does not claim a runner, a browser intake, a report renderer or a writeback connector exists. What ships today is v0: a post-processor over artefacts a finished import already wrote.
The divergences are enumerated in the specification, asserted in the manifest as a not-a-v1 flag with a list, and rendered as a visible block in your report. A cover note implying v1 compliance would invite you to hold us to v1’s receipt and rollback sections, and we would lose, correctly.
The eight divergences, named
- A flat directory, where v1 mandates nested rows, projections, receipts, control and diagnostics folders.
- A nineteen-column row header that deliberately does not reuse v1’s eighteen-column shape, and no v1 field name reused carrying a v0 meaning.
- The withheld ledger is pair grain, not row grain, because there is no incumbent proposal input for it to be about.
- Pairs are identified by a hash of the run and the sorted pair, not by lexicographic ordering, so the pack is not a function of your file’s row order — and a resumed import’s re-appended pairs de-duplicate.
- Canonicalised, hashed receipts sufficient for offline decision replay. v0 receipts are re-fetched and compared instead.
- A fifteen-column rollback manifest with before values, preconditions and inverse operations. v0 ships a reversal map, which is a different and smaller thing.
- Counterfactual sensitivity and merge blast radius. Two of the differentiating analyses on the roadmap are simply absent.
- Incumbent contradictions, and the authoritative-evidence and reviewer-confirmed bases for a cannot-link. Neither is wired, and the manifest says so.
Nothing is written to your systems. It is not a shadow run.
The no-write property is scoped to your systems, and that scope is exact: nothing is written to your CRM, your ERP, your MDM or your warehouse. The pack proposes actions; it never applies them.
It is not the v1 no-write attestation. The logs the pack post-processes came from a real import through the public batch endpoint, which minted entities, recorded decisions and consumed credits in our own catalogue.
Undoing a merge, and exactly where it stops
A merge is a decision with a receipt, and a decision with a receipt can be reversed. Today that reversal is a single-record detach: one record comes out of an over-merged company and becomes a fresh one containing itself. It is the safe correction for the common case — this CRM row belongs to a different company — and it is deliberately not a general split or a rollback of merge history.
POST /v1/entities/ent_01K2WQ4F7N0B3J5XGRV8YH6PDA/detach
Authorization: Bearer <review credential>
Content-Type: application/json
{
"record_id": "rec_01K3XJ7PZ8H4A2E9T6KMSD1VRC",
"reviewer_id": "analyst@example.com",
"reason": "Registry evidence confirms a separate legal company"
}
409 Conflict
{ "error": { "code": "entity_detach_unsafe", ... } } # before any writeSix preconditions are tested first, and any one of them failing returns a typed conflict — entity_detach_unsafe — before any write happens.
The six preconditions, all of which fail closed
- 01
The entity and the record are yours
The verified key supplies the tenant; it cannot be named in the body. Another tenant's entity is indistinguishable from a missing one and returns 404, not 403, so the route cannot be used to confirm that a competitor is a customer. - 02
At least one other member remains
Detaching the sole member is not a split, it is a rename with extra steps. - 03
Every member record is still present
The tenant's record store has to hold all of them. A cluster with a deleted member cannot be re-partitioned safely, because the missing record's facts cannot be attributed. - 04
Every customer fact has an owner, and the selected record owns at least one
Migration 0012 gave each fact the immutable id of the record that contributed it. Rows written before it are readable but were not backfilled — the contributing record cannot be reconstructed reliably, and assigning origins by value is not a safe backfill. An entity holding a legacy, unattributed customer fact therefore refuses detach outright and needs an explicit offline rebuild first. - 05
No remaining member has byte-identical resolution content
Fingerprint bindings are keyed by content, not by record id, so sending two identical members to different entities would make replay ambiguous. - 06
No other entity already claims the record
Membership is still stored as JSON, so the transactional adapter scans the tenant and refuses a duplicate. It will not mint a third membership.
What moves, what stays, what commits, and what it will not do
- what moves
- Facts contributed by the selected record move to the fresh entity. Identifier crosswalk and reviewer-derived alias facts are recomputed on both sides from the facts each side retains.
- what stays
- Unattributed registry, filing and research facts stay on the source entity. They are never discarded and never guessed onto the detached one: such evidence may well have been fetched while resolving the record being detached, but the schema proves its source, not which merge event introduced it.
- what commits
- One database transaction carries both entity memberships and fact sets, the blocking keys for both entities, the detached record's content-fingerprint binding recorded as human review at confidence 1.0, every stored client-reference link, invalidation of cached responses quoting the source, and an immutable non-match decision naming the reviewer, the reason, the acting credential and the detach itself as a feature.
- what it will not do
- Exactly one member per request. No arbitrary set split, no bridge analysis, no rollback to a historical cluster shape, no automatic reattach command, and the route is not HTTP-idempotent — after an uncertain network result, read the membership before retrying. The non-match receipt is a high-value negative label, but the matcher does not consume negative constraints yet: an exact-byte replay follows the corrected binding, while materially changed content runs the cascade again and could merge again.
These limits keep the workflow reversible without pretending the current data model can reconstruct merge history it never stored.
Preview reads your file first, and refuses to forecast
It is the first step of a Sprint, and we run it on the file you send. It sorts every row by the strongest retrieval signal that survives normalisation, counts what each column can and cannot contribute, and prices a ceiling on running the file. It reports what your file contains, never a predicted resolution rate: what identifiers a file holds is a fact about the file, and what the cascade will do with them is not. A test in the product fails the build if a predicted-coverage field ever appears in that output. It calls no API and spends no credits.
The full output: six tiers, the signals table, the two column blocks, two totals
# identifier composition — 4812 rows
register_key 1004
catalogue_key 318
name_qualified 2447
name_only 961
no_retrieval_signal 61
not_submittable 21
## signals (a row may carry several)
signal usable in file unusable
registration_number 988 1142 154 [register]
lei 16 16 0 [register]
domain 2231 2398 167 [catalogue]
postal_code 1942 1942 0 [qualifier]
## populated columns we could not use
These rows were tiered as if the column were empty. The column is NOT
missing from the file, so asking for it again is the wrong ask.
registration_number 154 of 1142 values did not survive normalization [register]
redacted shape of one: n/a
domain 167 of 2398 values did not survive normalization [catalogue]
redacted shape of one: <host>/<path>
## columns carrying nothing
mapped, and empty on every row: trading_names
not mapped, or empty and indistinguishable from absent: country, jurisdiction
rows with a usable name: 4791 (a name column was populated on 4812 rows; 21 of those normalized to nothing)
rows the API will accept: 4791What the composition table is telling you
- six tiers
- Register key, catalogue key, name qualified, name only, no retrieval signal, and not submittable. The tier is computed over the normalised record, because normalisation is what retrieval sees: a domain column holding N/A is an identifying field to the admission check and nothing at all to the blocking index, and a preview that counted it would have quoted a file it had mis-read.
- three kinds of empty
- Unmapped — send us the column. Mapped and empty on every row — the column is there and nobody fills it in. Populated with values that did not survive normalisation — you already have the data, in a shape we cannot read. The customer action differs completely between the three, so the report never collapses them into one number.
- the spend line
- The estimate is a conservative ceiling on a partly unmeasured basis, and it is printed under a provenance sentence that says so. That sentence travels with the figure into any proposal. A model identifier the rate card has never heard of is priced at the most expensive published rate and named in the unpriced list.
- what it costs you
- Nothing at all. Knowing what is in your file before anybody talks price is a reasonable thing to want, and it is why this step runs separately from the one that spends.
- your own upload
- Your Entity Bind workspace has an upload of its own, and it is a run rather than this preview. It takes a UTF-8 CSV of at most 1,000,000 bytes and, by default, 500 US or GB company rows, shows you five of them so you can map your columns — name is required; domain, country, registration_number, jurisdiction, lei, ticker, exchange, address, city, postcode and client_ref are optional — and works on the Free plan in Matching mode, which makes no registry or model calls; Matching and registry checks needs an active paid plan purchased in workspace Billing. Starting it stores the rows in your workspace, and a finished run downloads as CSV or JSON with its decision receipts.
We will run the preview against your file and send the table back before anyone talks about price.
Start freePrefer to put it to a person? Talk to us instead →
Five steps, each one consuming what the last one wrote
These are the steps we run on your file during a Sprint, in order. Wherever a step calls the service, it goes through the public HTTP API rather than the database, and nothing reaches a private path — so a successful import is evidence that your own integration will work. Three of the five can run without calling the API at all: the preview, the duplicate report and a pack assembled without receipts.
- 01
Preview
Qualify the file before anybody quotes it01 · previewreads leads.csv, with your columns mapped calls nothing returns the composition table and a spend ceilingWhat it costs, what comes back, and what to watch
Calls nothing and spends no credits, so it costs you nothing.
The composition table shown in full further up this page, and then a
SPEND BASIS:line and the spend ceiling as JSON — or the whole thing as one JSON report.It is the only step that runs before there is a commercial relationship at all, which is why it is separate from the one that spends. We run it on the file you send, and the table is what comes back.
- 02
Import
Resolve the file through the public batch endpoint02 · importreads leads.csv, keyed on your crm_id, Notes mapped to context calls POST /v1/batch, at the depth we agreed with you writes leads.csv.refs.csv, .skipped.csv, .pairs.csv, .state.jsonWhat it costs, what comes back, and what to watch
The only step that spends. Abstentions and byte-identical replays are free.
Four artefacts beside your file —
leads.csv.refs.csv(your key to our record and entity ids, with the decision, the stage, the decision id and the review id of every abstained row),leads.csv.skipped.csv,leads.csv.pairs.csvandleads.csv.state.json— then a JSON run report: rows in, rows skipped, entities out, duplicate clusters, largest cluster, automation rate, abstained, pairs withheld, pairs blocked, replayed and credits charged.Your own key column travels through as
client_refand comes back on every row, so the output joins to your data without matching on our ids. The automation rate’s denominator is rows the API answered, not rows in the file: a row the loader refused to submit was never offered to the cascade. The run is resumable, and it refuses to resume at a different depth, because two policies in one refs log would make a prescription mean one thing in the first half of a pack and another in the second. - 03
Duplicate report
Answer the question the buyer came with03 · duplicate reportreads leads.csv.refs.csv calls nothing writes clusters.csv, your rows grouped by the company they landed onWhat it costs, what comes back, and what to watch
Reads the refs log the import already wrote, so it calls nothing and spends nothing.
A cluster summary as JSON, and a CSV grouping your resolved rows by the company they landed on, with members labelled by your key values, not ours. A cluster is two or more rows by default. Run over the whole tenant catalogue instead of one file’s refs log, the members are record ids.
This is not a re-resolution. It reports what the cascade already decided: it submits nothing, changes nothing and costs nothing. “These six of your rows are one company” is the sentence a RevOps or MDM buyer opened the conversation with, and it is answered in their own identifiers.
- 04
Export
Take the golden records out, with their citations04 · exportcalls GET /v1/entities/export writes golden.csv, with provenance and licence columns writes ATTRIBUTION.txtWhat it costs, what comes back, and what to watch
Streams an endpoint you can call yourself. No resolution, no adjudication, no spend.
A fixed, documented column set that does not vary with your data, so whatever loads this file will keep loading it. Per-field
__source,__url,__source_as_ofand__last_checked_atcolumns, per-field__licenceand__permits_resale, and an ATTRIBUTION.txt.Provenance and licence are on by default, and licence handling fails closed: an unreadable licence reads as not-resellable, never as permitted. The governance columns are off by default, because they export names of natural persons. Personal identifiers — date of birth, nationality, country of residence, address — are dropped at ingestion and are never in the file whatever the setting says.
- 05
Audit pack
Assemble the bundle, reconcile it, and render the report05 · audit packreads leads.csv.refs.csv, leads.csv, leads.csv.state.json, golden.csv calls GET /v1/decisions/{id}, once per decision the pack cites writes pack/ — the bundle, with manifest.sha256 written lastWhat it costs, what comes back, and what to watch
Re-decides nothing. A pack rebuilds from a finished run without re-spending anything.
The bundle, and a JSON summary read straight back out of its own manifest: pack format, the not-a-v1 flag, run id, run status, totals, reconciliation, receipts, rights, absent ledgers, pair totals, prescriptions, anomalies, whether the report was written, and the file list.
A pack is built from the import’s refs log into an output directory of its own, and its run id is given explicitly or read from the state file or the source file. A named input file that does not exist is a hard error, never a silent skip: naming a file is a claim that it is there, and a typo in the state file’s path would otherwise publish a null identity as a check that could not be run. A pack can also be built entirely offline, without receipts, with no key and no call.
What the fifth step prints, on the published verification fixture
{
"not_audit_pack_v1": true,
"run_status": "complete",
"out_dir": "pack/",
"totals": {
"rows_in": 8,
"accepted_matches": 3,
"new_entities": 4,
"unresolved": 1,
"entities_out": 4,
"duplicate_clusters": 2
},
"reconciliation": {
"rows_in_partition": true,
"distinct_source_refs": true,
"golden_records_cover_targets": true,
"receipts_cover_decision_ids": true,
"every_unresolved_row_has_a_prescription": true
},
"absent_ledgers": {
"incumbent_contradictions.csv": "no_incumbent_proposal_input"
},
"report_written": true
}Two things to plan for: the window, and your notes column
The import is not a queue, and your notes column is worth mapping
Batches go through the batch endpoint one at a time, and a 300,000-row file is an overnight run — plan the window, not the throughput. Within one batch the order of rows no longer changes the answer; across batches it still does, because batch two has to see the companies batch one created.
If your file has a Notes or Description column, map it into context: it reaches the adjudicator as caller-supplied evidence, not as an identifying field, and a row carrying nothing but a name is what the cascade abstains on most. A row carrying only context is still skipped, because free text cannot say which company a row is.
Start with your own company records.
A Sprint runs over the public API on a copy of your file and writes nothing to your CRM, ERP, MDM or warehouse. You keep every file it produces.
300 credits. No card.