# Errors

> The 26 problem types the API returns, each with its HTTP status and what to do next.

Source: https://emailmovers.com/mydatahub/docs/errors

Every error is an [RFC 9457](https://www.rfc-editor.org/rfc/rfc9457.html) 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`](https://emailmovers.com/mydatahub/docs/errors/invalid-request.md) | 400 | The request could not be read | Send a JSON body with Content-Type: application/json and check the query string. |
| [`idempotency-key-required`](https://emailmovers.com/mydatahub/docs/errors/idempotency-key-required.md) | 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`](https://emailmovers.com/mydatahub/docs/errors/unsupported-version.md) | 400 | Unsupported MyDataHub-Version | Send a MyDataHub-Version date the API lists, such as 2027-01-15, or leave it out. |
| [`authentication-required`](https://emailmovers.com/mydatahub/docs/errors/authentication-required.md) | 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`](https://emailmovers.com/mydatahub/docs/errors/account-required.md) | 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`](https://emailmovers.com/mydatahub/docs/errors/insufficient-credits.md) | 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`](https://emailmovers.com/mydatahub/docs/errors/insufficient-scope.md) | 403 | The token lacks a scope | Ask the user to approve the scope named in required_scope, then retry. |
| [`confirmation-required`](https://emailmovers.com/mydatahub/docs/errors/confirmation-required.md) | 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`](https://emailmovers.com/mydatahub/docs/errors/business-details-required.md) | 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`](https://emailmovers.com/mydatahub/docs/errors/licence-acceptance-required.md) | 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`](https://emailmovers.com/mydatahub/docs/errors/dpa-acceptance-required.md) | 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`](https://emailmovers.com/mydatahub/docs/errors/agent-budget-exceeded.md) | 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`](https://emailmovers.com/mydatahub/docs/errors/not-found.md) | 404 | Not found | Check the id; ids are prefixed, such as ord_ for orders and aud_ for audiences. |
| [`method-not-allowed`](https://emailmovers.com/mydatahub/docs/errors/method-not-allowed.md) | 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`](https://emailmovers.com/mydatahub/docs/errors/idempotency-key-reused.md) | 409 | Idempotency-Key reused with different parameters | Use a new Idempotency-Key for a different request; reuse a key only to retry. |
| [`quote-expired`](https://emailmovers.com/mydatahub/docs/errors/quote-expired.md) | 409 | The machine quote has expired | Call quote_order (POST /v1/quotes) again; a machine quote is valid for 24 hours. |
| [`order-not-ready`](https://emailmovers.com/mydatahub/docs/errors/order-not-ready.md) | 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`](https://emailmovers.com/mydatahub/docs/errors/order-not-cancellable.md) | 409 | Only a queued order can be cancelled | Records already delivered stay under licence; report bounces for a warranty claim. |
| [`validation-failed`](https://emailmovers.com/mydatahub/docs/errors/validation-failed.md) | 422 | The request is not valid | Fix each entry in errors, using did_you_mean where given, and send it again. |
| [`quote-required`](https://emailmovers.com/mydatahub/docs/errors/quote-required.md) | 422 | A machine quote is required | Call quote_order first; a machine quote is valid for 24 hours. |
| [`dataset-unavailable`](https://emailmovers.com/mydatahub/docs/errors/dataset-unavailable.md) | 422 | The dataset is not served yet | Use uk_contacts; GET /v1/datasets shows when other datasets open. |
| [`rate-limited`](https://emailmovers.com/mydatahub/docs/errors/rate-limited.md) | 429 | Too many requests | Wait for the seconds in Retry-After, then retry. |
| [`internal-error`](https://emailmovers.com/mydatahub/docs/errors/internal-error.md) | 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`](https://emailmovers.com/mydatahub/docs/errors/not-implemented.md) | 501 | Not implemented yet | Use the operations that answer today; /v1/openapi.json lists every operation and how it is declared. |
| [`service-unavailable`](https://emailmovers.com/mydatahub/docs/errors/service-unavailable.md) | 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`](https://emailmovers.com/mydatahub/docs/errors/tps-screen-expired.md) | 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

```json
{
  "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](https://emailmovers.com/mydatahub/docs/rate-limits.md).
