Reference
Upload a suppression list
POST /v1/suppressions. Upload emails, domains, phones, company numbers or title terms, raw or already hashed.
POST /v1/suppressions
Upload emails, domains, phones, company numbers or title terms, raw or already hashed. Values are normalised and stored as SHA-256 hashes only. The list applies to every later count and order at no charge, and each count reports what it removed. The organisation accepts the Article 28 data processing agreement in force first (POST /v1/account/agreements, or dpa_accepted_version on this upload from a signed-in person); until then the answer is 403 dpa-acceptance-required and nothing is uploaded.
| Property | Value |
|---|---|
| Endpoint | POST https://mydatahub.emailmovers.com/v1/suppressions |
| Operation id | createSuppressionList |
| Cost | Free (0 credits) |
| Access | An API key or an OAuth 2.1 token with the scope suppressions:write. |
| Idempotency-Key | Optional. Makes a retry safe. |
| Tag | Suppressions |
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. |
Idempotency-Key | header | string | no | Makes the request safe to retry: the same key returns the stored response. Keys are kept for the life of the organisation. |
Body
| Member | Type | Required | Description |
|---|---|---|---|
name | string | yes | |
kind | enum: email, domain, phone, company_number, title_term | yes | |
values | array of string | yes | Raw values (trimmed, lower cased and hashed on arrival) or SHA-256 hex hashes. Domains may use a wildcard such as *.gmail.com; phones are E.164; company numbers are eight characters. |
hashed | boolean | no | True when values are already SHA-256 hashes, each 64 lower case hex characters. Default false. Default false. |
dpa_accepted_version | string | no | The version of the Article 28 data processing agreement the person uploading accepts: the account's agreements.dpa.version. The app sends it with the first upload, and again when a new version is in force. Only a signed-in person accepts it. |
Example request
curl -X POST "https://mydatahub.emailmovers.com/v1/suppressions" \
-H "Authorization: Bearer $MYDATAHUB_API_KEY" \
-H "Content-Type: application/json" \
--data '{
"name": "Existing customers",
"kind": "email",
"values": ["[email protected]", "[email protected]"]
}'Responses
201: The saved list and what was read.
Headers: RateLimit, RateLimit-Policy, Location.
| Member | Type | Always present | Description |
|---|---|---|---|
list | SuppressionList | yes | A named list of hashed emails, domains, phones, company numbers or title terms, applied to every count and order at no charge. |
list.id | string | yes | Format: a suppression list id: sup_ followed by a 26 character ULID in upper case Crockford base32, such as sup_01JB4H7Q2V7XN3M9R6T1Y5W8ZA. |
list.name | string | yes | |
list.kind | enum: email, domain, phone, company_number, title_term | yes | |
list.source | enum: upload, objections | yes | upload for a list you sent; objections for the organisation's own list, which gathers objections you report and database-wide objections on records you hold. |
list.row_count | integer (0 or more) | yes | |
list.created_at | string (date-time) | yes | |
list.updated_at | string (date-time) | yes | |
list.created_by | string or null | yes | |
list.added_since | array of SuppressionNotice | no | With since: the records added to this list after that time, for pull. |
rows_read | integer (0 or more) | yes | |
rows_valid | integer (0 or more) | yes | |
rows_skipped | array of object | yes | |
rows_skipped[].row | integer (1 or more) | yes | |
rows_skipped[].reason | string | yes |
Example response
{
"list": {
"id": "sup_01JB4H7Q2V7XN3M9R6T1Y5W8ZA",
"name": "Existing customers",
"kind": "email",
"source": "upload",
"row_count": 1198,
"created_at": "2026-09-20T14:02:00Z",
"updated_at": "2026-09-20T14:02:00Z",
"created_by": "usr_01JA0Q3V7XN3M9R6T1Y5W8ZAK8"
},
"rows_read": 1204,
"rows_valid": 1198,
"rows_skipped": [
{
"row": 17,
"reason": "Not an email address."
}
]
}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.
| Status | Problem codes | When |
|---|---|---|
| 400 | invalid-request, idempotency-key-required, unsupported-version | The request could not be read, or an Idempotency-Key is missing. |
| 401 | authentication-required, account-required | 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). |
| 403 | insufficient-scope, confirmation-required, business-details-required, licence-acceptance-required, dpa-acceptance-required, agent-budget-exceeded | The token lacks a scope (insufficient-scope, with required_scope and a WWW-Authenticate step-up challenge); no person confirmed the spend and no agent key covers it (confirmation-required, with an approval_url); an agent key's per-order cap or monthly budget does not cover it (agent-budget-exceeded); an account made by self-serve sign-up has not yet confirmed its business name and that it is a UK business, which its first order needs (business-details-required); the order needs the Data Licence and Sharing Agreement accepted at the version in force (licence-acceptance-required); or a suppression upload comes before the organisation has accepted the Article 28 data processing agreement in force (dpa-acceptance-required). Nothing is charged. |
| 409 | idempotency-key-reused, quote-expired, order-not-ready, order-not-cancellable | An Idempotency-Key reused with different parameters, an expired machine quote, or an object in the wrong state. |
| 422 | validation-failed, quote-required, dataset-unavailable | The request is not valid. errors lists every problem with a JSON pointer and did_you_mean. |
| 429 | rate-limited | Too many requests. Wait for Retry-After. |
| Any other failure | internal-error, service-unavailable, tps-screen-expired | 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. |