# Quote an order

> quote_order: Creates a machine quote for a count: the records you would receive after removing ones already held and suppressed, the credits and GBP price, the licence, the warranty and any blockers.

Source: https://emailmovers.com/mydatahub/docs/reference/mcp/quote_order

Creates a machine quote for a count: the records you would receive after removing ones already held and suppressed, the credits and GBP price, the licence, the warranty and any blockers. The quote_id lasts 24 hours and is bound to the dataset version; it is not a human sales quote, which is a 14 day PDF made in the app. Free and needs no account, and nothing is charged. Show the credits and price to the user, then call create_order with the quote_id. Lawful basis is UK GDPR Article 6(1)(f) legitimate interests; email audiences include corporate subscribers only by default (PECR), so sole traders and ordinary partnerships are left out unless the toggle is changed.

| Property | Value |
| --- | --- |
| Tool name | `quote_order` |
| Position in tools/list | 13 of 22 |
| Cost | Free |
| Scope | None. Works without an account. |
| Annotations | readOnlyHint true, destructiveHint false, idempotentHint true, openWorldHint false |
| Kind | Read only; idempotent, not destructive, closed world |
| REST equivalent | [`POST /v1/quotes`](https://emailmovers.com/mydatahub/docs/reference/rest/create-quote.md) |

> **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 is what `tools/list` returns from https://mydatahub.emailmovers.com/mcp once v2 launches.

## Input

The schema sets `additionalProperties` to false, so a member that is not listed is refused.

| Member | Type | Required | Description |
| --- | --- | --- | --- |
| `count_id` | string | yes | A count id from POST /v1/counts or count_audience. Either case is accepted; responses use upper case. |
| `field_set` | string | yes | A field set id from GET /v1/field-sets, such as standard. |
| `limit` | integer (1 to 1000000) | no | Records to buy; left out, the whole count. |
| `dedupe_against` | enum: `none`, `previous_orders`, `crm`, `previous_orders_and_crm` | no | Remove records you already hold: from earlier orders, from a connected CRM, or both. Default previous_orders when signed in. crm and previous_orders_and_crm need connection_id. Default `previous_orders`. |
| `connection_id` | string | no | The CRM connection to check, only with dedupe_against crm or previous_orders_and_crm. |
| `licence` | enum: `multi_use_12m`, `outright` | no | multi_use_12m (12 month multi-use, every plan) or outright (1.8 credits a record, proposed). Default `multi_use_12m`. |

## Example call

The `params` of a `tools/call` request:

```json
{
  "name": "quote_order",
  "arguments": {
    "count_id": "cnt_7K2PQ4MAXN3R6TBY5WDZ",
    "field_set": "standard",
    "limit": 5000,
    "dedupe_against": "previous_orders",
    "licence": "multi_use_12m"
  }
}
```

## Output

The result carries `structuredContent` that matches this schema, and one line of text that leads with the number.

| Member | Type | Always present | Description |
| --- | --- | --- | --- |
| `id` | string | yes | Format: a quote id: qte_ followed by a 26 character ULID in upper case Crockford base32, such as qte_01JB4M2R6T1Y5W8ZAK8Q2V7XN3. |
| `kind` | `machine` | yes |  |
| `organisation_id` | string or null | yes | The organisation the quote belongs to; create_order settles it only there. null for a quote made without an account, which the first organisation to confirm it takes. |
| `count_id` | string | yes | The count the quote was priced from. |
| `dataset` | enum: `uk_contacts`, `ie_contacts`, `global_contacts` | no | The dataset an audience draws from. uk_contacts is live; ie_contacts and global_contacts are listed but not yet served. |
| `dataset_version` | string | yes | The immutable weekly build a count is bound to, such as 2026-W39. The last 52 stay queryable. |
| `field_set` | string | no |  |
| `records` | integer (0 or more) | yes | Records after overlap and suppression; what you would receive. |
| `removed` | object | yes | Records left out and never charged. |
| `removed.already_held` | integer (0 or more) | yes |  |
| `removed.suppressed` | integer (0 or more) | yes |  |
| `credits_per_record` | number (0 or more) | yes | 1 for a complete UK record; 1.8 outright (proposed). |
| `credits_required` | number (0 or more) | yes | records times credits_per_record (creditsFor). |
| `credits_available` | number (0 or more) or null | yes | Your balance; null when signed out. |
| `balance_after` | number or null | yes | credits_available less credits_required; negative means a top-up is needed; null when signed out. |
| `gbp_ex_vat` | number (0 or more) | yes | What credits_required cost bought the cheapest way, at the per-contact scale or with credit packs, excluding VAT. The same for every caller; purchase says what this caller needs to buy. |
| `purchase` | `Purchase` | yes | How to buy the credits the order needs, worked out by one function (cheapestPurchase) everywhere a price is shown. The caller's own, like the balance: read fresh on every read of the quote. |
| `purchase.credits_to_buy` | integer (0 or more) | yes | credits_required less the balance: all of it when signed out, 0 when the balance covers the order. |
| `purchase.cheapest` | string or null | yes | The id of the cheapest option; null when nothing needs buying. |
| `purchase.gbp_ex_vat` | number (0 or more) | yes | What the cheapest option costs, excluding VAT; 0 when nothing needs buying. |
| `purchase.options` | array of `PurchaseOption` | yes | Cheapest first: the per-contact scale, and the credit pack (with any rest at the scale) when it is on sale. At the same price, the option that buys more credits comes first. |
| `licence` | `Licence` | yes | Licence terms as data, on every quote, order, records page and receipt. Records stay usable, pushable and re-downloadable free until reuse_until. |
| `licence.type` | enum: `multi_use_12m`, `outright` | yes | multi_use_12m (12 month multi-use, every plan) or outright (1.8 credits a record, proposed). |
| `licence.reuse_until` | string (date) or null | yes | Order date plus 12 months; null for outright. |
| `licence.resale_prohibited` | boolean | yes |  |
| `licence.permitted_channels` | array of enum: `email`, `telephone`, `post` | yes | email, telephone and post on standard terms. |
| `licence.warranty_url` | string (uri) | yes |  |
| `licence.terms_version` | string | yes | The version of the Data Licence and Sharing Agreement the licence is under, such as 1.0. |
| `licence.terms_url` | string (uri) | no | Where that version of the Data Licence and Sharing Agreement is published. |
| `warranty` | `Warranty` | yes | The bounce warranty that covers delivered records, with the threshold it was issued under. |
| `warranty.policy` | enum: `replace_over_threshold_hard_bounce`, `replace_any_hard_bounce_30d` | yes | replace_over_threshold_hard_bounce is the current policy: records are replaced when hard bounces exceed threshold_percent of an order. replace_any_hard_bounce_30d is proposed and not yet in force. The current policy's id replaced one that named the threshold, a change to this public enum made before v2 went live. |
| `warranty.threshold_percent` | number (0 to 100) | no | replace_over_threshold_hard_bounce only, and required there: the share of an order, in percent, that hard bounces must exceed before records are replaced. It is the threshold the document was issued under. |
| `warranty.url` | string (uri) | yes |  |
| `created_at` | string (date-time) | no |  |
| `expires_at` | string (date-time) | yes | 24 hours after creation. |
| `blockers` | array of `QuoteBlocker` | yes |  |
| `blockers[].code` | enum: `insufficient_credits`, `account_required`, `agent_budget_exceeded`, `dataset_unavailable`, `count_expired` | yes |  |
| `blockers[].detail` | string | yes |  |
| `blockers[].top_up_url` | string (uri) | no |  |
| `signature` | `QuoteSignature` | yes | The signature over the machine quote's price, so an agent can hand the quote to another agent and both can check it. Verify it with the key at jwks_url. |
| `signature.alg` | `ES256` | yes |  |
| `signature.kid` | string | yes | The signing key id in jwks_url. |
| `signature.jwks_url` | string (uri) | yes |  |
| `signature.jws` | string | yes | A compact JWS whose payload is the quote's id, organisation_id, count_id, dataset_version, records, credits_required, gbp_ex_vat, licence, expires_at. |

## Errors

A failed call returns `isError` true. The text content leads with the sentence a person acts on, then the whole problem as JSON, with `code`, `detail`, `next_step` and `did_you_mean`. A 401 and a step-up 403 are answered at HTTP level so the client can start authorisation. See [Errors](https://emailmovers.com/mydatahub/docs/errors.md).
