Platform
Errors
The 26 problem types the API returns, each with its HTTP status and what to do next.
Every error is an RFC 9457 problem with the media type application/problem+json. Its type is a URI under https://emailmovers.com/mydatahub/docs/errors/, and each of the pages below is that URI.
Two members go beyond the RFC: next_step says what to do, and did_you_mean lists the closest valid values. MCP tool errors reuse the same object with isError true. The text content leads with the sentence a person acts on, followed by the whole problem as JSON.
Problem types
| Code | HTTP | Title | Next step |
|---|---|---|---|
invalid-request | 400 | The request could not be read | Send a JSON body with Content-Type: application/json and check the query string. |
idempotency-key-required | 400 | An Idempotency-Key header is required | Send an Idempotency-Key header (a V4 UUID is a good choice) and reuse it on every retry. |
unsupported-version | 400 | Unsupported MyDataHub-Version | Send a MyDataHub-Version date the API lists, such as 2027-01-15, or leave it out. |
authentication-required | 401 | Sign in or send an API key | Send Authorization: Bearer with an mdh_ API key. Sign-in through https://mydatahub.emailmovers.com/id is planned. |
account-required | 401 | This needs a free account | Create a free account (100 credits, no card) for single company counts, more than 6 filters in one audience, free text title search or a radius under 5 miles. Free and trial accounts still see rounded small counts; exact figures come with a paid plan. |
insufficient-credits | 402 | Not enough credits | Top up at the top_up_url, then retry with the same machine quote_id (or the same enrichment request). |
insufficient-scope | 403 | The token lacks a scope | Ask the user to approve the scope named in required_scope, then retry. |
confirmation-required | 403 | A person must confirm this spend | Nothing was charged. Keep the machine quote_id and give the user the approval_url; retry with the confirmation_token it returns, or use an agent key whose limits cover the spend. |
business-details-required | 403 | Business details are needed before the first order | Nothing was charged. A person who owns the account confirms the business name and that it is a UK business, in the app's Settings or with PATCH /v1/account, then the same machine quote_id is ordered again. |
licence-acceptance-required | 403 | The data licence agreement must be accepted | Nothing was charged. A person accepts the version in force (the account's agreements.dlsa): by ticking the box on the quote or at approval_url, where they can also approve this spend, or, for an agent key that orders or enriches on its own, on the key. Then the same request is sent again. |
dpa-acceptance-required | 403 | The data processing agreement must be accepted | Nothing was uploaded. A person who owns the account accepts the Article 28 data processing agreement at the version in force (the account's agreements.dpa): in the app's Settings, with the box on the app's first upload, or with POST /v1/account/agreements. Then the same list is uploaded again. |
agent-budget-exceeded | 403 | The agent key budget does not cover this spend | Keep the machine quote_id and ask the key owner to approve it at approval_url, or top up at top_up_url. |
not-found | 404 | Not found | Check the id; ids are prefixed, such as ord_ for orders and aud_ for audiences. |
method-not-allowed | 405 | Method not allowed | Use a method named in the Allow header; every path starts /v1 and /v1/openapi.json lists them. |
idempotency-key-reused | 409 | Idempotency-Key reused with different parameters | Use a new Idempotency-Key for a different request; reuse a key only to retry. |
quote-expired | 409 | The machine quote has expired | Call quote_order (POST /v1/quotes) again; a machine quote is valid for 24 hours. |
order-not-ready | 409 | The order is not ready | Wait for the order.ready webhook, or poll GET /v1/orders/{id} until status is ready. |
order-not-cancellable | 409 | Only a queued order can be cancelled | Records already delivered stay under licence; report bounces for a warranty claim. |
validation-failed | 422 | The request is not valid | Fix each entry in errors, using did_you_mean where given, and send it again. |
quote-required | 422 | A machine quote is required | Call quote_order first; a machine quote is valid for 24 hours. |
dataset-unavailable | 422 | The dataset is not served yet | Use uk_contacts; GET /v1/datasets shows when other datasets open. |
rate-limited | 429 | Too many requests | Wait for the seconds in Retry-After, then retry. |
internal-error | 500 | Something went wrong on our side | Nothing was charged. Retry with the same Idempotency-Key; if it persists, quote the instance to support. |
not-implemented | 501 | Not implemented yet | Use the operations that answer today; /v1/openapi.json lists every operation and how it is declared. |
service-unavailable | 503 | The service is briefly unavailable | Wait for the seconds in Retry-After, then retry with the same Idempotency-Key. The status page shows incidents. |
tps-screen-expired | 503 | The TPS and CTPS screen is more than 28 days old | Nothing was charged. Email-only audiences carry on, with a field set without telephone and no phone. filter or breakdown. Telephone audiences return once a new dataset version is published; GET /v1/health shows the screen's age. |
Reading a problem
{
"type": "https://emailmovers.com/mydatahub/docs/errors/validation-failed",
"title": "The request is not valid",
"status": 422,
"detail": "Unknown field company.employees.",
"instance": "/v1/counts",
"code": "validation-failed",
"next_step": "Fix each entry in errors, using did_you_mean where given, and send it again.",
"did_you_mean": ["company.employee_band"],
"errors": [
{
"pointer": "/audience/where/all/0/field",
"code": "invalid_value",
"message": "Unknown field company.employees.",
"did_you_mean": ["company.employee_band"]
}
]
}Branch on code, never on detail or title, which are for people and may be reworded. A problem that spends nothing says so in next_step. A 4xx response never charges credits, and an order retried with the same Idempotency-Key is never charged twice.
Every response, errors included, carries the RateLimit and RateLimit-Policy headers. A 429 and a 503 also carry Retry-After. See Rate limits.