# Get a quote

> GET /v1/quotes/{id}. A machine quote (kind machine, 24 hours) or a human sales quote (kind sales, 14 days).

Source: https://emailmovers.com/mydatahub/docs/reference/rest/get-quote

`GET /v1/quotes/{id}`

A machine quote (kind machine, 24 hours) or a human sales quote (kind sales, 14 days). An agent in the same organisation can settle another agent's machine quote after checking its signature, dataset version and expiry; outside the organisation, re-quote.

| Property | Value |
| --- | --- |
| Endpoint | `GET https://mydatahub.emailmovers.com/v1/quotes/{id}` |
| Operation id | `getQuote` |
| Cost | Free (0 credits) |
| Access | No account needed. An API key or OAuth token with the scope `quotes:read` is optional and adds your organisation's own figures. |
| Tag | Quotes |

> **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 |
| --- | --- | --- | --- | --- |
| `id` | path | string | yes | Format: a quote id: qte_ followed by a 26 character ULID in upper case Crockford base32, such as qte_01JB4M2R6T1Y5W8ZAK8Q2V7XN3. |
| `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. |

## Example request

```bash
curl "https://mydatahub.emailmovers.com/v1/quotes/qte_01JB4P2V7XN3M9R6T1Y5W8ZAK8"
```

## Responses

### 200: The quote.

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

#### MachineQuote

| 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. |

#### SalesQuote

| 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` | `sales` | yes |  |
| `status` | enum: `draft`, `sent`, `accepted`, `paid`, `expired`, `withdrawn` | yes |  |
| `created_at` | string (date-time) | yes |  |
| `valid_until` | string (date) | yes | 14 days after creation. |
| `created_by` | object | yes |  |
| `created_by.role` | enum: `user`, `account_manager` | yes |  |
| `created_by.name` | string | yes |  |
| `customer` | string | yes | The organisation the quote is for. |
| `count_id` | string or null | yes |  |
| `field_set` | string | yes |  |
| `records` | integer (0 or more) | yes | Records the quote covers. |
| `credits_per_record` | number (0 or more) | yes | Credits a record under the licence: 1, or 1.8 outright (proposed). |
| `lines` | array of `SalesQuoteLine` | yes |  |
| `lines[].description` | string | yes |  |
| `lines[].quantity` | integer (0 or more) | yes |  |
| `lines[].unit_price_gbp` | number (0 or more) | yes | Pounds sterling excluding VAT, to two decimal places. |
| `lines[].amount_gbp` | number (0 or more) | yes | Pounds sterling excluding VAT, to two decimal places. |
| `subtotal_gbp_ex_vat` | number (0 or more) | yes | Pounds sterling excluding VAT, to two decimal places. |
| `vat_gbp` | number (0 or more) | yes | Pounds sterling excluding VAT, to two decimal places. |
| `total_gbp_inc_vat` | number (0 or more) | yes | Pounds sterling excluding VAT, to two decimal places. |
| `balance_after` | number or null | yes | The customer's credits after the order. Paying by card or invoice leaves the balance as it is; null when the customer has no account yet. |
| `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 |  |
| `payment_methods` | array of enum: `card`, `invoice` | yes |  |
| `pay_url` | string (uri) | yes | Pay by card or request an invoice link, no account needed. |
| `pdf_url` | string (uri) | yes |  |

#### Example response

```json
{
  "id": "qte_01JB4P2V7XN3M9R6T1Y5W8ZAK8",
  "kind": "sales",
  "status": "sent",
  "created_at": "2026-09-26T11:00:00Z",
  "valid_until": "2026-10-10",
  "created_by": {
    "role": "account_manager",
    "name": "Jordan Hale"
  },
  "customer": "Example Agency Ltd",
  "count_id": "cnt_7K2PQ4MAXN3R6TBY5WDZ",
  "field_set": "standard",
  "records": 12310,
  "credits_per_record": 1,
  "lines": [
    {
      "description": "12,310 UK records, standard field set, 12-month multi-use licence",
      "quantity": 12310,
      "unit_price_gbp": 0.22,
      "amount_gbp": 2708.2
    }
  ],
  "subtotal_gbp_ex_vat": 2708.2,
  "vat_gbp": 541.64,
  "total_gbp_inc_vat": 3249.84,
  "balance_after": 6200,
  "licence": {
    "type": "multi_use_12m",
    "reuse_until": "2027-09-26",
    "resale_prohibited": true,
    "permitted_channels": ["email", "telephone", "post"],
    "warranty_url": "https://emailmovers.com/trust/warranty",
    "terms_version": "2026-09"
  },
  "warranty": {
    "policy": "replace_over_threshold_hard_bounce",
    "threshold_percent": 10,
    "url": "https://emailmovers.com/trust/warranty"
  },
  "payment_methods": ["card", "invoice"],
  "pay_url": "https://mydatahub.emailmovers.com/quotes/qte_01JB4P2V7XN3M9R6T1Y5W8ZAK8/pay",
  "pdf_url": "https://mydatahub.emailmovers.com/quotes/qte_01JB4P2V7XN3M9R6T1Y5W8ZAK8.pdf"
}
```

## 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). |
| 404 | [`not-found`](https://emailmovers.com/mydatahub/docs/errors/not-found.md) | No such object for this organisation. |
| 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. |
