# Counts and breakdowns

> Count an audience for free and get Total, New to you and Emailable, the premium figure beside all records, up to three breakdowns, quality signals and a compliance summary. Small numbers are rounded for anonymous callers.

Source: https://emailmovers.com/mydatahub/docs/counts
Updated: 17 October 2026

A count costs 0 credits, needs no account and returns in one request: contacts and companies together, three headline views, the premium figure beside all records, up to three breakdowns, and a content-addressed id that replays for 12 months. `POST /v1/counts` and `count_audience` take an audience, a `level` and up to 3 `breakdown_by` fields. The same audience gives the same number in the app, over REST and over MCP.

> **Not yet live.** MyDataHub v2 is built and tested locally and is not yet live. This page describes the contract the API is tested against, and the launch date is to confirm.

## Run a count

Send an [audience definition](https://emailmovers.com/mydatahub/docs/audience.md) inline, or the `aud_` id of a saved one.

```json
{
  "audience": { "schema": "mydatahub.audience/1", "dataset": "uk_contacts", "where": { "field": "company.sic_2007", "op": "in", "values": ["C"] } },
  "level": "contact",
  "breakdown_by": ["contact.job_level_rank"]
}
```

`level` is `contact` (the default) or `company`. Contacts and companies come back together either way, so "12,840 contacts at 3,115 companies" is one call. See [REST reference: Count an audience](https://emailmovers.com/mydatahub/docs/reference/rest/create-count.md) for every member.

## Three views

Every count carries the same three headline views, and `contacts` is the Total.

| View | Member | What it counts |
| --- | --- | --- |
| Total | `contacts`, `companies` | Every matching contact at an active company, with or without an email |
| New to you | `new_to_you` | Total, less records from your earlier orders and a connected CRM. `null` when signed out |
| Emailable | `emailable` | A named email scoring 70 or more at a corporate subscriber |

`views` repeats all three with contacts and companies each. It is the number to use before ordering, because records you already hold are never charged.

## Premium and all records

Every count carries the premium figure beside the full one, so no number changes meaning without saying so.

| Member | What it counts |
| --- | --- |
| `premium` | The contacts of `all_records` that are premium records. `null` when the dataset version carries no premium data, or withheld when the premium records are at a single company and the caller has no account |
| `all_records` | The full figure: the audience with its top-level premium filters taken out. A premium filter inside `any` or `not` stays. The same as `contacts` when it has none |
| `premium_note` | What premium means on this dataset version, or why `premium` is `null` |

`views.premium` and `views.all_records` give contacts and companies for each. Both are rounded for anonymous and trial callers exactly as every other figure is. Where the premium records are at a single company, an anonymous caller gets `premium` as `null`, with the reason in `premium_note`, and the rest of the count as usual.

A premium record is at an active company and has a LinkedIn profile. At launch it is a record in the premium export of a stated date, the dataset version's as-of date; later it will be a record validated within the last 12 months, from the validations feed. Filter on it with `contact.premium`, described in [The audience definition](https://emailmovers.com/mydatahub/docs/audience.md#premium-records).

A count runs your audience exactly as you send it, and the server never adds a premium filter. The app opens a new count with the premium chip on, and `POST /v1/audiences/compile` and `compile_audience` add `contact.premium` by default when the dataset version carries premium data, and say so in the readback. Remove the chip, or say "all records" in the brief, to count every record.

On a dataset version without premium data, `premium` is `null`, `premium_note` says so, and a `contact.premium` filter gets `422 validation-failed` with the reason. It never counts 0 for want of data.

## Defaults and toggles

The server applies four defaults and reports each one in `defaults`, with whether it was applied, the views it shapes and how many records it removed.

| `defaults` id | Removes | Shapes |
| --- | --- | --- |
| `active_companies` | Dissolved and insolvent companies | Every view |
| `named_emails` | Generic addresses such as `info@` | Emailable |
| `corporate_subscribers` | Sole traders and ordinary partnerships, which are individual subscribers under PECR | Emailable |
| `no_registered_phones` | Numbers a screen found TPS or CTPS registered, when a phone is requested; numbers not screened stay in | Every view |

The numbers behind the toggles let a person or an agent see what a default costs before turning it off. Premium is not one of these defaults: it is a filter in the audience, shown with the figure beside it (see [Premium and all records](#premium-and-all-records)). Your own suppression lists are applied on top and reported per list in `compliance_summary.suppressed_removed`.

## Breakdowns

Up to 3 fields in `breakdown_by` run as one grouped query. Each field returns its top 50 cells and an `other` bucket. Two or three fields also return a `crosstab`, capped at 5,000 cells and 200 KB.

```json
{
  "by": "contact.job_level_rank",
  "cells": [
    { "value": "director", "contacts": 4120 }
  ]
}
```

Hierarchical fields break down at one level: SIC by section and postcode by district. A seniority breakdown shows contacts with no known level last, as `not_known` labelled Level not known, with `filter_value` 0. A cell count is a whole number, the text `under 10`, or `suppressed` where a cell of 10 or more is hidden so that totals cannot be differenced.

Over MCP a cross-tab holds at most 200 cells, with `truncated` true when the cap cut it. Ask for fewer dimensions to see the rest.

## Disclosure control

Small numbers can identify people, so what you see depends on who you are. This is the suppress-and-round practice used for UK statistical outputs, as in [OpenSAFELY disclosure control](https://docs.opensafely.org/outputs/sdc/).

| Caller | Counts | Breakdown cells |
| --- | --- | --- |
| Anonymous | Counts under 100 rounded to the nearest 5 | Every cell rounded to the nearest 5, under 10 shown as `under 10` |
| Free or trial account | The same as anonymous | The same as anonymous |
| Paid account | Exact | Exact, with under 10 shown as `under 10` |

Every response that carries counts includes a `disclosure` object with its `level` (`exact` or `rounded`), `rounded_to` and `secondary_suppression`, so you can tell which figures you are reading. Single-company counts need an account and are logged.

A preview is withheld when the count is under 25. See [Samples](https://emailmovers.com/mydatahub/docs/samples.md).

## Quality signals

Each count shows how good the records are, with the date of measurement, so "verified" is never shown without a date.

| Member | What it measures |
| --- | --- |
| `quality.email_coverage_pct` | Records with a named email as a share of the count |
| `quality.verified_90d_pct` | Share of emails with a provider result of ok in the last 90 days |
| `quality.deliverability` | Distribution: good is 70 to 100, risky is 40 to 69, poor is under 40 |
| `quality.avg_days_since_verification` | Mean age of the latest verification result |
| `quality.phone_coverage_pct`, `quality.tps_ctps_screened_at` | Phone coverage and the version's TPS and CTPS screen date, when a phone is requested |

## Compliance summary

Every count returns `compliance_summary` beside the number, so you can reason about lawfulness before you spend. It holds the lawful basis (UK GDPR Article 6(1)(f) legitimate interests) and counts of corporate subscribers, individual subscribers excluded, TPS and CTPS numbers excluded, Article 14 notified and pending, and records removed per suppression list. See [Compliance receipts](https://emailmovers.com/mydatahub/docs/compliance-receipts.md).

## Price estimate

`estimate` gives the credits an order of the whole count would need and the price in GBP ex VAT two ways: `per_contact_scale`, every credit at the scale, and `cheapest`, the cheapest real purchase at the scale or with credit packs. It is an estimate only. A [machine quote](https://emailmovers.com/mydatahub/docs/quotes-and-orders.md) is the price you pay.

## Replay a count

`GET /v1/counts/{id}` returns the effective definition, the compiled plan (expanded terms, resolved centres, SIC codes behind a business class, suppression checksums), the pinned `dataset_version` and `engine_rules_version`, all three views with premium and all records, and the number on the current dataset. The `ETag` equals the id, so `If-None-Match` answers 304 without running the engine.

A count expires 12 months after it ran. See [Versions and reproducibility](https://emailmovers.com/mydatahub/docs/versions.md).

An objection or erasure request reaches every count the same day through an overlay. A pinned re-run reports `removed_since` beside the original number.

## Next steps

- Look at who is in the count with [Samples](https://emailmovers.com/mydatahub/docs/samples.md).
- Price it with [Quotes and orders](https://emailmovers.com/mydatahub/docs/quotes-and-orders.md).
- Save and share it with `POST /v1/audiences`, then count by `aud_` id.
