EmailmoversMyDataHub docs
Browse the docs

Reference / REST reference

Reference

Quote an order

POST /v1/quotes. A signed machine quote for a count, field set, quantity and licence: records after overlap and suppression, credits per record, credits, the GBP price, the balance after, the licence and warranty, and blockers.

Generated from the contractsView as Markdown

POST /v1/quotes

A signed machine quote for a count, field set, quantity and licence: records after overlap and suppression, credits per record, credits, the GBP price, the balance after, the licence and warranty, and blockers. Valid 24 hours and bound to a dataset version and the caller's organisation. Free, and needs no account. This is not a human sales quote, which is a 14 day PDF made in the app.

PropertyValue
EndpointPOST https://mydatahub.emailmovers.com/v1/quotes
Operation idcreateQuote
CostFree (0 credits)
AccessNo account needed. An API key or OAuth token with the scope quotes:read is optional and adds your organisation's own figures.
Idempotency-KeyOptional. Makes a retry safe.
MCP toolquote_order
TagQuotes

Request

Parameters

NameInTypeRequiredDescription
MyDataHub-VersionheaderstringnoThe 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-KeyheaderstringnoMakes the request safe to retry: the same key returns the stored response. Keys are kept for the life of the organisation.

Body

MemberTypeRequiredDescription
count_idstringyesA count id from POST /v1/counts or count_audience. Either case is accepted; responses use upper case.
field_setstringyesA field set id from GET /v1/field-sets, such as standard.
limitinteger (1 to 1000000)noRecords to buy; left out, the whole count.
dedupe_againstenum: none, previous_orders, crm, previous_orders_and_crmnoRemove 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_idstringnoThe CRM connection to check, only with dedupe_against crm or previous_orders_and_crm.
licenceenum: multi_use_12m, outrightnomulti_use_12m (12 month multi-use, every plan) or outright (1.8 credits a record, proposed). Default multi_use_12m.

Example request

curl -X POST "https://mydatahub.emailmovers.com/v1/quotes" \
  -H "Content-Type: application/json" \
  --data '{
  "count_id": "cnt_7K2PQ4MAXN3R6TBY5WDZ",
  "field_set": "standard",
  "limit": 5000,
  "dedupe_against": "previous_orders",
  "licence": "multi_use_12m"
}'

Responses

201: The machine quote.

Headers: RateLimit, RateLimit-Policy, Location.

MemberTypeAlways presentDescription
idstringyesFormat: a quote id: qte_ followed by a 26 character ULID in upper case Crockford base32, such as qte_01JB4M2R6T1Y5W8ZAK8Q2V7XN3.
kindmachineyes
organisation_idstring or nullyesThe 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_idstringyesThe count the quote was priced from.
datasetenum: uk_contacts, ie_contacts, global_contactsnoThe dataset an audience draws from. uk_contacts is live; ie_contacts and global_contacts are listed but not yet served.
dataset_versionstringyesThe immutable weekly build a count is bound to, such as 2026-W39. The last 52 stay queryable.
field_setstringno
recordsinteger (0 or more)yesRecords after overlap and suppression; what you would receive.
removedobjectyesRecords left out and never charged.
removed.already_heldinteger (0 or more)yes
removed.suppressedinteger (0 or more)yes
credits_per_recordnumber (0 or more)yes1 for a complete UK record; 1.8 outright (proposed).
credits_requirednumber (0 or more)yesrecords times credits_per_record (creditsFor).
credits_availablenumber (0 or more) or nullyesYour balance; null when signed out.
balance_afternumber or nullyescredits_available less credits_required; negative means a top-up is needed; null when signed out.
gbp_ex_vatnumber (0 or more)yesWhat 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.
purchasePurchaseyesHow 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_buyinteger (0 or more)yescredits_required less the balance: all of it when signed out, 0 when the balance covers the order.
purchase.cheapeststring or nullyesThe id of the cheapest option; null when nothing needs buying.
purchase.gbp_ex_vatnumber (0 or more)yesWhat the cheapest option costs, excluding VAT; 0 when nothing needs buying.
purchase.optionsarray of PurchaseOptionyesCheapest 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.
licenceLicenceyesLicence terms as data, on every quote, order, records page and receipt. Records stay usable, pushable and re-downloadable free until reuse_until.
licence.typeenum: multi_use_12m, outrightyesmulti_use_12m (12 month multi-use, every plan) or outright (1.8 credits a record, proposed).
licence.reuse_untilstring (date) or nullyesOrder date plus 12 months; null for outright.
licence.resale_prohibitedbooleanyes
licence.permitted_channelsarray of enum: email, telephone, postyesemail, telephone and post on standard terms.
licence.warranty_urlstring (uri)yes
licence.terms_versionstringyesThe version of the Data Licence and Sharing Agreement the licence is under, such as 1.0.
licence.terms_urlstring (uri)noWhere that version of the Data Licence and Sharing Agreement is published.
warrantyWarrantyyesThe bounce warranty that covers delivered records, with the threshold it was issued under.
warranty.policyenum: replace_over_threshold_hard_bounce, replace_any_hard_bounce_30dyesreplace_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_percentnumber (0 to 100)noreplace_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.urlstring (uri)yes
created_atstring (date-time)no
expires_atstring (date-time)yes24 hours after creation.
blockersarray of QuoteBlockeryes
blockers[].codeenum: insufficient_credits, account_required, agent_budget_exceeded, dataset_unavailable, count_expiredyes
blockers[].detailstringyes
blockers[].top_up_urlstring (uri)no
signatureQuoteSignatureyesThe 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.algES256yes
signature.kidstringyesThe signing key id in jwks_url.
signature.jwks_urlstring (uri)yes
signature.jwsstringyesA compact JWS whose payload is the quote's id, organisation_id, count_id, dataset_version, records, credits_required, gbp_ex_vat, licence, expires_at.

Example response

{
  "id": "qte_01JB4M2R6T1Y5W8ZAK8Q2V7XN3",
  "kind": "machine",
  "dataset_version": "2026-W39",
  "records": 5000,
  "removed": {
    "already_held": 312,
    "suppressed": 41
  },
  "credits_required": 5000,
  "credits_available": 6200,
  "gbp_ex_vat": 1249,
  "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"
  },
  "expires_at": "2026-09-27T10:14:00Z",
  "blockers": [],
  "organisation_id": "org_01JA0Q2V7XN3M9R6T1Y5W8ZAK8",
  "count_id": "cnt_7K2PQ4MAXN3R6TBY5WDZ",
  "credits_per_record": 1,
  "balance_after": 1200,
  "purchase": {
    "credits_to_buy": 0,
    "cheapest": null,
    "gbp_ex_vat": 0,
    "options": []
  },
  "signature": {
    "alg": "ES256",
    "kid": "qte-2026-09",
    "jwks_url": "https://mydatahub.emailmovers.com/.well-known/jwks.json",
    "jws": "eyJhbGciOiJFUzI1NiIsImtpZCI6InF0ZS0yMDI2LTA5In0.eyJpZCI6InF0ZV8wMUpCNE0yUjZUMVk1VzhaQUs4UTJWN1hOMyJ9.MEUCIQDexampleSignatureOnly"
  }
}

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.

StatusProblem codesWhen
400invalid-request, idempotency-key-required, unsupported-versionThe request could not be read, or an Idempotency-Key is missing.
401authentication-required, account-requiredSign in (OAuth) or send an API key (authentication-required), or create a free account for what the anonymous tier does not cover (account-required).
409idempotency-key-reused, quote-expired, order-not-ready, order-not-cancellableAn Idempotency-Key reused with different parameters, an expired machine quote, or an object in the wrong state.
422validation-failed, quote-required, dataset-unavailableThe request is not valid. errors lists every problem with a JSON pointer and did_you_mean.
429rate-limitedToo many requests. Wait for Retry-After.
Any other failureinternal-error, service-unavailable, tps-screen-expiredSomething 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.