Guide
Versions and reproducibility
A count id re-runs exactly for 12 months on its pinned dataset version. The API evolves by dated version, and the path major changes only for breaking changes.
Three things are versioned, and each is pinned so a result can be reproduced. The API has a dated MyDataHub-Version, the data has an immutable weekly dataset_version, and the counting rules have an engine_rules_version. A count id hashes the last two, so the same definition on the same data gives the same id for 12 months.
API versions
The base is https://mydatahub.emailmovers.com/v1. Within /v1, behaviour changes by date.
- Send
MyDataHub-Version: 2027-01-15to pin a version. Leave it out and your key'sapi_versionapplies. - Non-breaking changes ship as a new date. An unsupported date answers unsupported-version.
- The path major changes only for a breaking change, and the old major stays available for 12 months. There are no
/v2paths. - Response objects are open. A new member may appear in a later version, so ignore members you do not know. Request bodies are closed and refuse unknown members.
- A webhook endpoint is pinned to the version of the request that created it, and every event carries its
api_version.
2027-01-15 is the only version so far, and it is the one these docs describe. Lists use cursor pagination and return {data, has_more, next_cursor}, with limit defaulting to 25 and a maximum of 100 (500 on records).
Dataset versions
Every Monday the pipeline builds an immutable dataset_version, such as 2026-W39, and flips the current pointer. The last 52 stay queryable, so any count from the last 12 months re-runs exactly.
The plan is for Companies House changes to land within 15 minutes through its streaming feeds and to appear in the next build. The streams are not built: the build reads Companies House data as files.
Opt-outs, objections and erasure requests do not wait for Monday. A same-day overlay is subtracted from every count and delivery. A count replayed on its pinned version therefore reports removed_since beside the original number.
GET /v1/datasets lists each dataset with its owner, source, licence, refresh, residency, regulator, warranty and credits_per_record. Version 2.0 serves uk_contacts only. ie_contacts and global_contacts are listed but not yet served, and asking for them answers dataset-unavailable.
Each version publishes what changed at GET /v1/datasets/uk_contacts/versions/2026-W39.
| Member | What it holds |
|---|---|
row_counts | Companies, sites, contacts, emails and phones in the build |
changes | Records added, retired and re-verified, companies dissolved, job changes confirmed and suppressions applied |
coverage_pct | Coverage by field |
input_snapshots | The source snapshots the build used, with their dates |
checksum | A checksum of the build |
How a count id is made
A count id is content addressed. It is cnt_ followed by the first 20 base32 characters of the SHA-256 of three things in RFC 8785 canonical JSON:
- The effective definition, after the server fills its defaults and puts it in canonical form.
- The
dataset_version. - The
engine_rules_version.
The canonical form sorts and deduplicates in and since values, terms and expansion sets, flattens nested groups of the same kind, unwraps a group of one and a double not, and upper cases postcodes. A bundle is replaced by its fragment.
The definition reaches the same id from chips, REST or MCP, so there is one id and one cache entry. The id is the same for every caller, level and breakdown.
Replay a count
GET /v1/counts/{id} returns the count as it ran, worked out again for the caller's own tier, so an anonymous caller who knows a paid caller's id sees rounded numbers. It returns:
- the effective definition and the compiled plan, with the expanded terms, the resolved centroid and cells, and the SIC codes behind a business class,
- the pinned
dataset_versionandengine_rules_version, - all three views, the breakdowns, the compliance summary and the quality block,
removed_since, the Total as it ran less the Total now, andexpires_at, 12 months after the count ran.
The ETag equals the id, so a client that holds a count can ask with If-None-Match and get 304. A count that read your organisation's private suppression lists belongs to your organisation, and anyone else gets 404.
Quotable counts
Because a count replays, it can be cited. The app shows each count at mydatahub.emailmovers.com/counts/{id} with a "Cite this count" button.
It copies a line such as "Source: MyDataHub by Emailmovers, 1,605,275 named UK business contacts, as of 26 September 2026" and the count URL. We never put a count or a price behind sign-up.
Reproducibility checklist
- Save the
idof every count you rely on. - Keep the
dataset_versionandengine_rules_versionit returned. - Quote against the count id, so the quote is bound to the same dataset version.
- Re-run with
GET /v1/counts/{id}within 12 months, and readremoved_since.
Next steps
- Count with Counts and breakdowns.
- Read what changed and what is going in Changelog and deprecation policy.
- See the headers and the cache rules in Rate limits.