EmailmoversMyDataHub docs
Browse the docs

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.

Updated 4 October 2026View as Markdown

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-15 to pin a version. Leave it out and your key's api_version applies.
  • 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 /v2 paths.
  • 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.

MemberWhat it holds
row_countsCompanies, sites, contacts, emails and phones in the build
changesRecords added, retired and re-verified, companies dissolved, job changes confirmed and suppressions applied
coverage_pctCoverage by field
input_snapshotsThe source snapshots the build used, with their dates
checksumA 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:

  1. The effective definition, after the server fills its defaults and puts it in canonical form.
  2. The dataset_version.
  3. 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_version and engine_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, and
  • expires_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 id of every count you rely on.
  • Keep the dataset_version and engine_rules_version it 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 read removed_since.

Next steps