# Preview sample records

> POST /v1/previews. Up to 25 masked sample rows from a seed fixed per audience, so a repeat returns the same rows.

Source: https://emailmovers.com/mydatahub/docs/reference/rest/create-preview

`POST /v1/previews`

Up to 25 masked sample rows from a seed fixed per audience, so a repeat returns the same rows. Each row has verification badges, why it matches and a provenance card with the compliance object. No rows when the count is under 25. Free.

| Property | Value |
| --- | --- |
| Endpoint | `POST https://mydatahub.emailmovers.com/v1/previews` |
| Operation id | `createPreview` |
| Cost | Free (0 credits) |
| Access | No account needed. An API key or OAuth token with the scope `previews:read` is optional and adds your organisation's own figures. |
| MCP tool | [`preview_records`](https://emailmovers.com/mydatahub/docs/reference/mcp/preview_records.md) |
| Tag | Counts |

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

## Request

### Parameters

| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `MyDataHub-Version` | header | string | no | The dated API version, pinned per key when left out. Non-breaking changes ship as new dates; the path major changes only for breaking changes, with a 12 month deprecation window. |

### Body

| Member | Type | Required | Description |
| --- | --- | --- | --- |
| `audience` | string or `Audience` | no | An audience definition (mydatahub.audience/1) inline, or the aud_ id of a saved audience. |
| `count_id` | string | no | A count id instead of an audience. |
| `limit` | integer (1 to 25) | no | Rows to return, 1 to 25. Default 10. Default `10`. |
| `shuffle` | boolean | no | Draw a new seed. Counts against your rate budget and is logged. Default false, so a repeat returns the same rows. Default `false`. |

## Example request

```bash
curl -X POST "https://mydatahub.emailmovers.com/v1/previews" \
  -H "Content-Type: application/json" \
  --data '{
  "count_id": "cnt_7K2PQ4MAXN3R6TBY5WDZ",
  "limit": 10
}'
```

## Responses

### 200: The sample.

Headers: `RateLimit`, `RateLimit-Policy`.

| Member | Type | Always present | Description |
| --- | --- | --- | --- |
| `id` | string | yes | Format: a preview id: prv_ followed by a 26 character ULID in upper case Crockford base32, such as prv_01JB4H3M7Q2V7XN3M9R6T1Y5W8. |
| `count_id` | string | yes | Format: a count id: cnt_ followed by 20 base32 characters (A to Z and 2 to 7), such as cnt_7K2PQ4MAXN3R6TBY5WDZ. |
| `dataset_version` | string | yes | The immutable weekly build a count is bound to, such as 2026-W39. The last 52 stay queryable. |
| `seed` | string | yes | The seed the rows came from; the same audience gives the same seed. |
| `count` | integer (0 or more) | yes | The count the rows were drawn from, rounded as disclosure says. |
| `disclosure` | `Disclosure` | yes | Whether these numbers are exact or protected: "under 10" cells, rounding to 5 and secondary suppression for anonymous and trial callers. |
| `disclosure.level` | enum: `exact`, `rounded` | yes | exact for authenticated paid callers; rounded for anonymous and trial callers, whose counts under 100 and every cell are rounded to the nearest 5. |
| `disclosure.rounded_to` | integer or null | yes | 5 when numbers are rounded; null when they are exact. |
| `disclosure.secondary_suppression` | boolean | yes | True when cross-tab or breakdown cells of 10 or more were hidden as "suppressed" so totals cannot be differenced. |
| `disclosure.notes` | array of string | yes |  |
| `rows` | array of `PreviewRow` | yes |  |
| `rows[].id` | string | yes | A session-scoped id standing in for the record. It never reveals contact_ ids. |
| `rows[].title` | string | yes |  |
| `rows[].job_level` | string or null | yes | The level, such as Director; null when the level is not known. |
| `rows[].job_function` | string or null | yes |  |
| `rows[].company` | object | yes |  |
| `rows[].site` | object | yes |  |
| `rows[].email_masked` | string or null | yes | First character of the local part plus the top level domain, such as j••••@••••.co.uk. |
| `rows[].email_type` | enum: `named`, `generic_local_part`, `generic_domain` or null | yes |  |
| `rows[].phone_masked` | string or null | yes | To the area code, such as 0113 ••• •••1. |
| `rows[].tps_ctps_status` | enum: `clear`, `tps_registered`, `ctps_registered`, `tps_or_ctps_registered`, `not_screened` or null | yes |  |
| `rows[].badges` | array of `VerifiedBadge` or `OtherBadge` | yes |  |
| `rows[].why_it_matches` | array of `WhyItMatches` | yes |  |
| `rows[].provenance` | `ProvenanceCard` | yes | The ICO due diligence answers for one record, as shown on the sample row. |
| `rows[].compliance` | `ComplianceObject` | yes | Carried by every preview row, order record, lookup and enrichment, and repeated as CSV columns and as the MyDataHub property group on CRM push. |
| `withheld` | object or null | yes | Rows held back: all of them when the count is under 25, or rows whose title fewer than 10 people hold. |
| `withheld.rows` | integer (0 or more) | yes |  |
| `withheld.reason` | string | yes |  |
| `masked` | `true` | yes |  |
| `watermark` | string or null | yes | The account id watermarked into sample CSVs; null when signed out. |
| `created_at` | string (date-time) | yes |  |

#### Example response

```json
{
  "id": "prv_01JB4H3M7Q2V7XN3M9R6T1Y5W8",
  "count_id": "cnt_7K2PQ4MAXN3R6TBY5WDZ",
  "dataset_version": "2026-W39",
  "seed": "7k2pq4ma",
  "count": 12840,
  "rows": [
    {
      "id": "prv_01JB4H3N2V7XN3M9R6T1Y5W8ZA",
      "title": "Marketing Director",
      "job_level": "Director",
      "job_function": "Marketing",
      "company": {
        "name": "Example Manufacturing Ltd",
        "companies_house_number": "12345678",
        "status": "active",
        "employee_band": "100-199",
        "sic_2007": ["C"]
      },
      "site": {
        "town": "Leeds",
        "postcode_district": "LS11",
        "region": "yorkshire_and_the_humber"
      },
      "email_masked": "j••••@••••.co.uk",
      "email_type": "named",
      "phone_masked": "0113 ••• •••1",
      "tps_ctps_status": "clear",
      "badges": [
        {
          "badge": "verified",
          "date": "2026-09-10",
          "label": "Verified 10 September 2026"
        }
      ],
      "why_it_matches": [
        {
          "pointer": "/where/all/0",
          "field": "contact.title",
          "value": "Marketing Director",
          "explanation": "Marketing Director matched marketing manager through expansion."
        },
        {
          "pointer": "/where/all/3",
          "field": "company.employee_band",
          "value": "100-199",
          "explanation": "Employees 120, inside 50 or more."
        }
      ],
      "provenance": {
        "sources": {
          "title": "emailmovers_research",
          "company": "companies_house",
          "email": "emailmovers_research"
        },
        "first_seen_at": "2026-05-02",
        "last_verified_at": "2026-08-30",
        "method": "telephone",
        "companies_house_number": "12345678",
        "company_status": "active",
        "article14_status": "notified",
        "tps_ctps_screened_at": "2026-09-12",
        "lawful_basis": "legitimate_interests_art6_1_f"
      },
      "compliance": {
        "lawful_basis": "legitimate_interests_art6_1_f",
        "lia_ref": "EM-LIA-2026-03",
        "subscriber_class": "ltd",
        "corporate_subscriber": true,
        "email_marketing_basis": "pecr_corporate_subscriber",
        "subscriber_evidence": {
          "companies_house_number": "12345678",
          "company_type": "ltd"
        },
        "article14_status": "notified",
        "article14_notified_at": "2026-06-14",
        "notice_version": "2026-02",
        "provenance": {
          "company": {
            "source": "companies_house",
            "as_of": "2026-09-21"
          },
          "contact": {
            "source": "emailmovers_research",
            "compiled_at": "2026-05-02",
            "last_verified_at": "2026-08-30",
            "method": "telephone"
          },
          "email": {
            "verified_at": "2026-09-10",
            "result": "ok",
            "provider_location": "EU"
          }
        },
        "tps_ctps_status": "clear",
        "tps_ctps_screened_at": "2026-09-12",
        "tps_ctps_valid_until": "2026-10-10",
        "suppression_status": "none",
        "record_age_days": 147,
        "attribution": "Contains public sector information licensed under the Open Government Licence v3.0"
      }
    }
  ],
  "withheld": null,
  "masked": true,
  "watermark": null,
  "created_at": "2026-09-26T10:15:00Z",
  "disclosure": {
    "level": "rounded",
    "rounded_to": 5,
    "secondary_suppression": false,
    "notes": [
      "Counts under 100 and every breakdown cell are rounded to the nearest 5 for anonymous and trial callers."
    ]
  }
}
```

## Errors

Every error is a problem object with a `code` and a `next_step`. Every response carries the `RateLimit` and `RateLimit-Policy` headers; see [Rate limits](https://emailmovers.com/mydatahub/docs/rate-limits.md).

| Status | Problem codes | When |
| --- | --- | --- |
| 400 | [`invalid-request`](https://emailmovers.com/mydatahub/docs/errors/invalid-request.md), [`idempotency-key-required`](https://emailmovers.com/mydatahub/docs/errors/idempotency-key-required.md), [`unsupported-version`](https://emailmovers.com/mydatahub/docs/errors/unsupported-version.md) | The request could not be read, or an Idempotency-Key is missing. |
| 401 | [`authentication-required`](https://emailmovers.com/mydatahub/docs/errors/authentication-required.md), [`account-required`](https://emailmovers.com/mydatahub/docs/errors/account-required.md) | Sign in (OAuth) or send an API key (authentication-required), or create a free account for what the anonymous tier does not cover (account-required). |
| 422 | [`validation-failed`](https://emailmovers.com/mydatahub/docs/errors/validation-failed.md), [`quote-required`](https://emailmovers.com/mydatahub/docs/errors/quote-required.md), [`dataset-unavailable`](https://emailmovers.com/mydatahub/docs/errors/dataset-unavailable.md) | The request is not valid. errors lists every problem with a JSON pointer and did_you_mean. |
| 429 | [`rate-limited`](https://emailmovers.com/mydatahub/docs/errors/rate-limited.md) | Too many requests. Wait for Retry-After. |
| Any other failure | [`internal-error`](https://emailmovers.com/mydatahub/docs/errors/internal-error.md), [`service-unavailable`](https://emailmovers.com/mydatahub/docs/errors/service-unavailable.md), [`tps-screen-expired`](https://emailmovers.com/mydatahub/docs/errors/tps-screen-expired.md) | Something failed on our side (500 internal-error) or the service is briefly unavailable (503 service-unavailable, with Retry-After). Nothing was charged; retry with the same Idempotency-Key. A count, sample, quote or order with telephone in play is also refused with 503 tps-screen-expired, with no Retry-After, while the dataset version's TPS and CTPS screen is more than 28 days old. |
