openapi: 3.1.1
info:
  title: MyDataHub API
  version: 2.0.0
  summary: UK B2B contact counts, quotes and lists, by Emailmovers.
  description: |-
    MyDataHub by Emailmovers, the UK B2B count engine. Looking is free: the catalogue, counts, breakdowns, masked previews, machine quotes and plans need no account. One credit buys one complete UK record at the same price here, in the app and in the MCP server at https://mydatahub.emailmovers.com/mcp.

    Every spend follows a signed machine quote (24 hours) and a human confirmation or a pre-authorised agent key; a confirmed flag set by a model is never consent, and a standard API key or OAuth token alone never spends. x-cost-credits on every operation gives its credit cost, and x-free marks the free ones.

    Errors are RFC 9457 problem details with next_step and did_you_mean. Rate limits use the IETF RateLimit and RateLimit-Policy fields, with Retry-After on 429 and 503. Lists return {data, has_more, next_cursor}. Response objects may gain members in a later MyDataHub-Version: ignore members you do not know.
  contact:
    name: MyDataHub by Emailmovers Ltd
    url: https://emailmovers.com/mydatahub/docs
  license:
    name: MyDataHub Data Licence and Sharing Agreement (terms version 2026-09)
    url: https://mydatahub.emailmovers.com/licence.json
  x-mydatahub-version: "2027-01-15"
servers:
  - url: https://mydatahub.emailmovers.com/v1
    description: Production. Test keys (mdh_test_) return synthetic data and never charge.
security:
  - OAuth2: []
  - ApiKey: []
tags:
  - name: Catalogue
    description: "Free and public: plans, datasets, filter fields and values, field sets, title expansion, location resolution and compiling a brief."
  - name: Counts
    description: "Free and public: counts with breakdowns, and masked samples (previews)."
  - name: Samples
    description: A sample of a counted audience, sent only after a person confirms a work email address, and downloaded once.
  - name: Audiences
    description: Saved audiences shared across the organisation.
  - name: Quotes
    description: Machine quotes (24 hours, free and public) and human sales quotes (14 days).
  - name: Orders
    description: Spend credits against a machine quote; status, cancellation and the signed compliance receipt.
  - name: Records
    description: Delivered records under licence, as pages or a signed download. Free.
  - name: Lookups
    description: Company lookup (free) and contact enrichment (1 credit on a confident match).
  - name: Suppressions
    description: Suppression lists, checks before a send, and objections.
  - name: Compliance
    description: "Public: the trust document, the LIA and compliance receipt verification."
  - name: Account
    description: Balance, plan, usage, credits bought by card, and ending a rolling plan.
  - name: Keys
    description: API keys and agent keys with budgets.
  - name: CRM push
    description: CRM connections, the New to you overlap check and record pushes.
  - name: Webhooks
    description: Webhook endpoints and the 30 day event log.
  - name: Meta
    description: This document and the health check.
externalDocs:
  description: Guides and the MCP server
  url: https://emailmovers.com/mydatahub/docs
x-rate-limits:
  - tier: anonymous
    counts: 60 an hour and 600 a day per IP and fingerprint
    other: Challenge after 20; at most 6 filters an audience; no single company counts, free text title search or radius under 5 miles; counts under 100 and breakdown cells rounded to the nearest 5
    status: confirmed
  - tier: free
    counts: 60 a minute and 1,000 a day per organisation
    other: Counts under 100 and breakdown cells rounded to the nearest 5
    status: to_confirm
  - tier: paid
    counts: Published per plan
    other: Exact counts
    status: confirmed
paths:
  /plans:
    get:
      operationId: getPlans
      summary: Get plans and prices
      description: Plans, credit packs, credit rules, licence terms, the guarantee and the free trial, in GBP excluding VAT. The same content as https://mydatahub.emailmovers.com/pricing.json. One credit buys one complete UK record at the same price in the app, API and MCP server.
      tags:
        - Catalogue
      security: []
      parameters:
        - $ref: "#/components/parameters/MyDataHubVersion"
      responses:
        "200":
          description: Plans and prices.
          headers:
            RateLimit:
              $ref: "#/components/headers/RateLimit"
            RateLimit-Policy:
              $ref: "#/components/headers/RateLimit-Policy"
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Pricing"
              examples:
                illustrative:
                  summary: Today's plans and credit packs
                  value:
                    version: 2026-10
                    valid_from: "2026-10-13"
                    currency: GBP
                    vat_treatment: Prices exclude VAT at 20 percent.
                    credit_definition: 1 credit buys 1 complete UK record, delivered once, at the per-contact scale's rate, the same in the app, API and MCP server.
                    price_scale:
                      id: per_contact_scale
                      name: Per-contact scale
                      status: confirmed
                      available_to: everyone
                      minimum_order_records: 0
                      rule: The credits bought, after any balance, pick one band, and every credit in the purchase is at its rate. A quote compares the scale with the credit pack and leads with the cheaper.
                      bands:
                        - from_records: 1
                          to_records: 2999
                          gbp_per_record: 0.25
                        - from_records: 3000
                          to_records: 9999
                          gbp_per_record: 0.25
                        - from_records: 10000
                          to_records: 24999
                          gbp_per_record: 0.22
                        - from_records: 25000
                          to_records: 49999
                          gbp_per_record: 0.195
                        - from_records: 50000
                          to_records: null
                          gbp_per_record: 0.17
                    plans:
                      - id: free
                        name: Free
                        kind: free
                        status: confirmed
                        available_to: everyone
                        price_gbp_ex_vat: 0
                        billing_period: once
                        credits: 100
                        per_record_gbp: null
                        credits_expire: false
                        for_whom: Triallists, human or agent
                        notes: No card
                      - id: pay_as_you_go
                        name: Pay as you go
                        kind: subscription
                        status: confirmed
                        available_to: existing_customers
                        price_gbp_ex_vat: 299
                        billing_period: month
                        credits: 1000
                        per_record_gbp: 0.299
                        rollover_cap_credits: 12000
                        credits_expire: false
                        for_whom: Existing customers, who keep this price
                        notes: Cancel any time. Not sold to new customers since 13 October 2026.
                      - id: plan_12_month
                        name: 12-month plan
                        kind: subscription
                        status: confirmed
                        available_to: existing_customers
                        price_gbp_ex_vat: 249
                        billing_period: month
                        annual_price_gbp_ex_vat: 2988
                        credits: 1000
                        per_record_gbp: 0.249
                        rollover_cap_credits: 12000
                        credits_expire: false
                        for_whom: Existing customers, who keep this price
                        notes: Not sold to new customers since 13 October 2026.
                      - id: subscription_2000
                        name: Subscription
                        kind: subscription
                        status: confirmed
                        available_to: new_customers
                        price_gbp_ex_vat: 500
                        billing_period: month
                        contract_months: 12
                        annual_price_gbp_ex_vat: 6000
                        credits: 2000
                        per_record_gbp: 0.25
                        rollover_cap_credits: 0
                        credits_expire: true
                        for_whom: Teams that buy every month
                        notes: A 12-month contract. Each month's 2,000 records are used or lost; records beyond them cost the quote's cheapest purchase.
                    credit_packs:
                      - id: credit_pack_40000
                        name: Credit pack 40,000
                        kind: pack
                        status: confirmed
                        available_to: everyone
                        price_gbp_ex_vat: 1249
                        billing_period: once
                        credits: 40000
                        per_record_gbp: 0.031
                        credits_expire: false
                        for_whom: Anyone buying about 5,000 records or more
                    credit_rules:
                      - action: count
                        label: Count, breakdown, preview, machine quote, catalogue, title expansion
                        credits: 0
                        unit: call
                        status: confirmed
                      - action: record
                        label: Full UK contact record delivered, any channel or field set
                        credits: 1
                        unit: record
                        status: confirmed
                      - action: company_only_record
                        label: Company-only record (no named contact)
                        credits: 0.1
                        unit: record
                        status: proposed
                      - action: enrich_contact
                        label: Contact enrichment, match found
                        credits: 1
                        unit: match
                        status: confirmed
                        note: 0 if already licensed
                      - action: lookup_company
                        label: Company lookup, no contact details
                        credits: 0
                        unit: call
                        status: to_confirm
                      - action: outright
                        label: Outright ownership
                        credits: 1.8
                        unit: record
                        status: proposed
                    licence:
                      - licence_type: multi_use_12m
                        status: confirmed
                        credits_per_record: 1
                        permitted_channels:
                          - email
                          - telephone
                          - post
                        reuse_months: 12
                        resale_prohibited: true
                        warranty_url: https://emailmovers.com/trust/warranty
                        terms_version: 2026-09
                    guarantee:
                      current:
                        policy: replace_over_threshold_hard_bounce
                        threshold_percent: 10
                        url: https://emailmovers.com/trust/warranty
                      proposed:
                        policy: replace_any_hard_bounce_30d
                        url: https://emailmovers.com/trust/warranty
                      refunds:
                        - Credits and credit packs are not refundable once bought, except where the law requires.
                        - Existing customers keep what their signed agreement says until it renews.
                    trial:
                      credits: 100
                      card_required: false
                      expires: false
                    meters:
                      - action: record
                        unit: record
                        credits: 1
                        status: confirmed
                      - action: enrich_contact
                        unit: match
                        credits: 1
                        status: confirmed
                      - action: company_only_record
                        unit: record
                        credits: 0.1
                        status: proposed
                      - action: outright
                        unit: record
                        credits: 1.8
                        status: proposed
                      - action: count
                        unit: call
                        credits: 0
                        status: confirmed
                    action_path:
                      - step: 1
                        action: quote_order
                        rest: POST /v1/quotes
                        description: Get a signed machine quote for a count, valid 24 hours. Free.
                      - step: 2
                        action: create_order
                        rest: POST /v1/orders
                        description: Spend credits against the quote_id with a person's confirmation or an agent key within its limits.
                      - step: 3
                        action: https://mydatahub.emailmovers.com/billing/top-up
                        rest: null
                        description: When the balance is short, buy one of the quote's purchase options (the cheapest comes first) through Stripe Checkout, then retry step 2.
                    promises:
                      - key: looking_is_free
                        statement: Looking is free forever
                        in_practice: Filters, counts, breakdowns, masked previews, machine quotes, plans and trust documents need no account or card.
                      - key: never_pay_twice
                        statement: Never pay twice
                        in_practice: Re-downloads under licence, duplicates, suppressed records, records you already hold and failed lookups cost 0.
        "400":
          $ref: "#/components/responses/BadRequest"
        "429":
          $ref: "#/components/responses/RateLimited"
        default:
          $ref: "#/components/responses/ServerError"
      x-cost-credits: 0
      x-free: true
      x-anonymous: true
      x-mcp-tool: get_plans
  /datasets:
    get:
      operationId: listDatasets
      summary: List datasets
      description: Every dataset with owner, source, licence, refresh, residency, regulator, warranty and credits per record. v2.0 serves uk_contacts; the others are listed with their status.
      tags:
        - Catalogue
      security: []
      parameters:
        - $ref: "#/components/parameters/MyDataHubVersion"
        - name: limit
          in: query
          required: false
          description: Items per page, 1 to 100. Default 25.
          schema:
            default: 25
            type: integer
            minimum: 1
            maximum: 100
        - name: cursor
          in: query
          required: false
          description: An opaque cursor from next_cursor. Pass it back unchanged.
          schema:
            type: string
            minLength: 1
            maxLength: 512
      responses:
        "200":
          description: A page of datasets.
          headers:
            RateLimit:
              $ref: "#/components/headers/RateLimit"
            RateLimit-Policy:
              $ref: "#/components/headers/RateLimit-Policy"
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/DatasetList"
              examples:
                illustrative:
                  summary: The UK dataset
                  value:
                    data:
                      - id: uk_contacts
                        name: UK contacts
                        status: live
                        owner: Emailmovers Ltd
                        source: Emailmovers research; Companies House (OGL v3.0); ONSPD (OGL v3.0)
                        licence: 12 month multi-use licence; resale prohibited
                        refresh: Immutable weekly build every Monday; same-day suppression overlay
                        residency: Held in the UK (Google Cloud London)
                        regulator: ICO
                        warranty: Replacement when hard bounces exceed 10 percent of an order
                        credits_per_record: 1
                        credits_status: confirmed
                        current_version: 2026-W39
                        versions_available: 52
                    has_more: false
                    next_cursor: null
        "400":
          $ref: "#/components/responses/BadRequest"
        "422":
          $ref: "#/components/responses/ValidationFailed"
        "429":
          $ref: "#/components/responses/RateLimited"
        default:
          $ref: "#/components/responses/ServerError"
      x-cost-credits: 0
      x-free: true
      x-anonymous: true
  /datasets/{dataset}/versions/{version}:
    get:
      operationId: getDatasetVersion
      summary: Get a dataset version changelog
      description: "What changed in one immutable weekly build: records added, retired and re-verified, companies dissolved, job changes confirmed, suppressions applied, coverage by field and the input snapshots."
      tags:
        - Catalogue
      security: []
      parameters:
        - $ref: "#/components/parameters/MyDataHubVersion"
        - name: dataset
          in: path
          required: true
          description: The dataset an audience draws from. uk_contacts is live; ie_contacts and global_contacts are listed but not yet served.
          schema:
            type: string
            enum:
              - uk_contacts
              - ie_contacts
              - global_contacts
          example: uk_contacts
        - name: version
          in: path
          required: true
          description: The immutable weekly build a count is bound to, such as 2026-W39. The last 52 stay queryable.
          schema:
            type: string
            pattern: ^[0-9]{4}-W(0[1-9]|[1-4][0-9]|5[0-3])$
            examples:
              - 2026-W39
          example: 2026-W39
      responses:
        "200":
          description: The version's changelog.
          headers:
            RateLimit:
              $ref: "#/components/headers/RateLimit"
            RateLimit-Policy:
              $ref: "#/components/headers/RateLimit-Policy"
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/DatasetVersionChangelog"
              examples:
                illustrative:
                  summary: The 2026-W39 build
                  value:
                    dataset: uk_contacts
                    version: 2026-W39
                    built_at: "2026-09-21T04:00:00Z"
                    checksum: b94d27b9934d3e08a52e52d7da7dabfac484efe37a5380ee9088f7ace2efcde9
                    row_counts:
                      companies: 5689369
                      sites: 3232415
                      contacts: 9028587
                      emails: 1660190
                      phones: 1210000
                    changes:
                      records_added: 4120
                      records_retired: 1870
                      records_reverified: 118400
                      companies_dissolved: 15130
                      job_changes_confirmed: 210
                      suppressions_applied: 96
                    coverage_pct:
                      named_email: 17.8
                      email: 18.4
                    input_snapshots:
                      - source: companies_house
                        as_of: "2026-09-01"
                      - source: onspd
                        as_of: "2026-08-01"
        "400":
          $ref: "#/components/responses/BadRequest"
        "404":
          $ref: "#/components/responses/NotFound"
        "429":
          $ref: "#/components/responses/RateLimited"
        default:
          $ref: "#/components/responses/ServerError"
      x-cost-credits: 0
      x-free: true
      x-anonymous: true
  /catalogue/filters:
    get:
      operationId: listFilters
      summary: List filter fields
      description: Every filterable field of mydatahub.audience/1 with its group, type, operators, value count and default, plus the operators and the dataset list. Free and public, cached for 24 hours.
      tags:
        - Catalogue
      security: []
      parameters:
        - $ref: "#/components/parameters/MyDataHubVersion"
        - name: dataset
          in: query
          required: false
          description: The dataset an audience draws from. uk_contacts is live; ie_contacts and global_contacts are listed but not yet served.
          schema:
            type: string
            enum:
              - uk_contacts
              - ie_contacts
              - global_contacts
      responses:
        "200":
          description: The filter catalogue.
          headers:
            RateLimit:
              $ref: "#/components/headers/RateLimit"
            RateLimit-Policy:
              $ref: "#/components/headers/RateLimit-Policy"
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/FilterCatalogue"
              examples:
                illustrative:
                  summary: Filter catalogue
                  value:
                    dataset: uk_contacts
                    dataset_version: 2026-W39
                    fields:
                      - field: company.employee_band
                        group: company
                        type: band
                        label: Employee band
                        description: Company size in 13 bands, 1-5 to 10000+.
                        operators:
                          - in
                          - between
                          - gte
                          - lte
                        facet: true
                        value_count: 14
                        default: null
                        examples:
                          - 50-99
                        values_url: /v1/catalogue/filters/company.employee_band/values
                    operators:
                      - op: gte
                        applies_to: banded and ranked fields
                        description: The value and everything after it in the order (for bands, larger bands).
                    datasets:
                      - id: uk_contacts
                        name: UK contacts
                        status: live
                        owner: Emailmovers Ltd
                        source: Emailmovers research; Companies House (OGL v3.0); ONSPD (OGL v3.0)
                        licence: 12 month multi-use licence; resale prohibited
                        refresh: Immutable weekly build every Monday; same-day suppression overlay
                        residency: Held in the UK (Google Cloud London)
                        regulator: ICO
                        warranty: Replacement when hard bounces exceed 10 percent of an order
                        credits_per_record: 1
                        credits_status: confirmed
                        current_version: 2026-W39
                        versions_available: 52
        "400":
          $ref: "#/components/responses/BadRequest"
        "422":
          $ref: "#/components/responses/ValidationFailed"
        "429":
          $ref: "#/components/responses/RateLimited"
        default:
          $ref: "#/components/responses/ServerError"
      x-cost-credits: 0
      x-free: true
      x-anonymous: true
      x-mcp-tool: list_filters
  /catalogue/filters/{dimension}/values:
    get:
      operationId: searchFilterValues
      summary: Search filter values
      description: Values of one field, searched by text, each with its contact and company count, plus did_you_mean for a near miss. The dimension is the field name, such as site.town.
      tags:
        - Catalogue
      security: []
      parameters:
        - $ref: "#/components/parameters/MyDataHubVersion"
        - name: dimension
          in: path
          required: true
          description: A field name from GET /v1/catalogue/filters, such as site.town.
          schema:
            type: string
            pattern: ^(company|site|contact|email|phone|signal)\.[a-z0-9_]+$
          example: site.town
        - name: q
          in: query
          required: false
          description: Text to search for.
          schema:
            type: string
            minLength: 1
            maxLength: 100
          example: leeds
        - name: dataset
          in: query
          required: false
          description: The dataset an audience draws from. uk_contacts is live; ie_contacts and global_contacts are listed but not yet served.
          schema:
            type: string
            enum:
              - uk_contacts
              - ie_contacts
              - global_contacts
        - name: limit
          in: query
          required: false
          description: Items per page, 1 to 100. Default 25.
          schema:
            default: 25
            type: integer
            minimum: 1
            maximum: 100
        - name: cursor
          in: query
          required: false
          description: An opaque cursor from next_cursor. Pass it back unchanged.
          schema:
            type: string
            minLength: 1
            maxLength: 512
      responses:
        "200":
          description: A page of values.
          headers:
            RateLimit:
              $ref: "#/components/headers/RateLimit"
            RateLimit-Policy:
              $ref: "#/components/headers/RateLimit-Policy"
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/FilterValueList"
              examples:
                illustrative:
                  summary: Towns matching leeds
                  value:
                    field: site.town
                    dataset_version: 2026-W39
                    data:
                      - code: leeds
                        label: Leeds
                        contacts: 41210
                        companies: 9870
                        parent: west_yorkshire
                    has_more: false
                    next_cursor: null
                    disclosure:
                      level: rounded
                      rounded_to: 5
                      secondary_suppression: false
                      notes:
                        - Counts under 100 and every breakdown cell are rounded to the nearest 5 for anonymous and trial callers.
        "400":
          $ref: "#/components/responses/BadRequest"
        "404":
          $ref: "#/components/responses/NotFound"
        "422":
          $ref: "#/components/responses/ValidationFailed"
        "429":
          $ref: "#/components/responses/RateLimited"
        default:
          $ref: "#/components/responses/ServerError"
      x-cost-credits: 0
      x-free: true
      x-anonymous: true
      x-mcp-tool: search_filter_values
  /field-sets:
    get:
      operationId: listFieldSets
      summary: List field sets
      description: "The named bundles of fields an order can deliver, with their exact columns. Every field set costs 1 credit a record: no telephone, export or API surcharge."
      tags:
        - Catalogue
      security: []
      parameters:
        - $ref: "#/components/parameters/MyDataHubVersion"
        - name: limit
          in: query
          required: false
          description: Items per page, 1 to 100. Default 25.
          schema:
            default: 25
            type: integer
            minimum: 1
            maximum: 100
        - name: cursor
          in: query
          required: false
          description: An opaque cursor from next_cursor. Pass it back unchanged.
          schema:
            type: string
            minLength: 1
            maxLength: 512
      responses:
        "200":
          description: A page of field sets.
          headers:
            RateLimit:
              $ref: "#/components/headers/RateLimit"
            RateLimit-Policy:
              $ref: "#/components/headers/RateLimit-Policy"
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/FieldSetList"
              examples:
                illustrative:
                  summary: The standard field set
                  value:
                    data:
                      - id: standard
                        name: Standard
                        description: Named email, postal address and company fields with Companies House number and SIC.
                        credits_per_record: 1
                        columns:
                          - name: first_name
                            description: First name
                          - name: email
                            description: Named business email
                          - name: companies_house_number
                            description: Companies House number
                    has_more: false
                    next_cursor: null
        "400":
          $ref: "#/components/responses/BadRequest"
        "422":
          $ref: "#/components/responses/ValidationFailed"
        "429":
          $ref: "#/components/responses/RateLimited"
        default:
          $ref: "#/components/responses/ServerError"
      x-cost-credits: 0
      x-free: true
      x-anonymous: true
      x-mcp-tool: list_field_sets
  /job-titles/expand:
    post:
      operationId: expandJobTitles
      summary: Expand job titles
      description: Show every title a keyword matches through the expansion sets (plurals, department synonyms, ampersands, abbreviations, word order), each with a count, or what a bundle such as sdm resolves to. Nothing is resolved silently.
      tags:
        - Catalogue
      security: []
      parameters:
        - $ref: "#/components/parameters/MyDataHubVersion"
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/TitleExpansionRequest"
            examples:
              illustrative:
                summary: Expand marketing manager
                value:
                  terms:
                    - marketing manager
                  mode: contains
      responses:
        "200":
          description: The expanded titles.
          headers:
            RateLimit:
              $ref: "#/components/headers/RateLimit"
            RateLimit-Policy:
              $ref: "#/components/headers/RateLimit-Policy"
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/TitleExpansion"
              examples:
                illustrative:
                  summary: Expand marketing manager
                  value:
                    dataset_version: 2026-W39
                    mode: contains
                    expand:
                      - plural_singular
                      - department_synonyms
                      - ampersand_and
                      - abbreviations
                    terms:
                      - term: Marketing Manager
                        from_term: marketing manager
                        expansion_set: input
                        contacts: 6210
                      - term: Marketing Managers
                        from_term: marketing manager
                        expansion_set: plural_singular
                        contacts: 14
                      - term: Mktg Manager
                        from_term: marketing manager
                        expansion_set: abbreviations
                        contacts: under 10
                    total_contacts: 6230
                    bundle: null
                    filter:
                      field: contact.title
                      op: matches
                      terms:
                        - marketing manager
                      mode: contains
                      expand:
                        - plural_singular
                        - department_synonyms
                        - ampersand_and
                        - abbreviations
                    disclosure:
                      level: rounded
                      rounded_to: 5
                      secondary_suppression: false
                      notes:
                        - Counts under 100 and every breakdown cell are rounded to the nearest 5 for anonymous and trial callers.
        "400":
          $ref: "#/components/responses/BadRequest"
        "422":
          $ref: "#/components/responses/ValidationFailed"
        "429":
          $ref: "#/components/responses/RateLimited"
        default:
          $ref: "#/components/responses/ServerError"
      x-cost-credits: 0
      x-free: true
      x-anonymous: true
      x-mcp-tool: expand_job_titles
  /locations/resolve:
    post:
      operationId: resolveLocation
      summary: Resolve a location
      description: Turn free text into region, county, town, postcode or radius filters. Ambiguous names come back as candidates with a question, never a silent choice. A radius under 5 miles needs an account (401 account-required).
      tags:
        - Catalogue
      security:
        - {}
        - OAuth2:
            - counts:read
        - ApiKey:
            - counts:read
      parameters:
        - $ref: "#/components/parameters/MyDataHubVersion"
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/LocationRequest"
            examples:
              illustrative:
                summary: Twenty miles of Leeds
                value:
                  text: 20 miles of Leeds
      responses:
        "200":
          description: Candidates for the place.
          headers:
            RateLimit:
              $ref: "#/components/headers/RateLimit"
            RateLimit-Policy:
              $ref: "#/components/headers/RateLimit-Policy"
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/LocationResolution"
              examples:
                illustrative:
                  summary: Twenty miles of Leeds
                  value:
                    text: 20 miles of Leeds
                    ambiguous: true
                    question: "Leeds: city centre or the metropolitan district?"
                    candidates:
                      - kind: radius
                        code: "E08000035"
                        label: Within 20 miles of Leeds (metropolitan district centroid)
                        contacts: 42318
                        centre:
                          lat: 53.7997
                          lon: -1.5492
                        filter:
                          field: site.location
                          op: within_radius
                          centre:
                            place: Leeds
                            code: "E08000035"
                          radius_miles: 20
                      - kind: radius
                        code: LS1
                        label: Within 20 miles of Leeds city centre (LS1)
                        contacts: 41960
                        centre:
                          lat: 53.7974
                          lon: -1.5438
                        filter:
                          field: site.location
                          op: within_radius
                          centre:
                            postcode: LS1
                          radius_miles: 20
                    disclosure:
                      level: rounded
                      rounded_to: 5
                      secondary_suppression: false
                      notes:
                        - Counts under 100 and every breakdown cell are rounded to the nearest 5 for anonymous and trial callers.
        "400":
          $ref: "#/components/responses/BadRequest"
        "401":
          $ref: "#/components/responses/Unauthorized"
        "422":
          $ref: "#/components/responses/ValidationFailed"
        "429":
          $ref: "#/components/responses/RateLimited"
        default:
          $ref: "#/components/responses/ServerError"
      x-cost-credits: 0
      x-free: true
      x-anonymous: true
      x-mcp-tool: resolve_location
  /audiences/compile:
    post:
      operationId: compileAudience
      summary: Compile a brief
      description: Turn a plain English brief into an audience definition with a one-sentence readback, the chips it made and any questions to answer first. Words that match no filter are returned, not guessed.
      tags:
        - Catalogue
      security: []
      parameters:
        - $ref: "#/components/parameters/MyDataHubVersion"
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/BriefRequest"
            examples:
              illustrative:
                summary: Finance directors at law firms in Manchester
                value:
                  brief: 300 finance directors at law firms in Manchester
      responses:
        "200":
          description: The compiled audience.
          headers:
            RateLimit:
              $ref: "#/components/headers/RateLimit"
            RateLimit-Policy:
              $ref: "#/components/headers/RateLimit-Policy"
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/CompiledBrief"
              examples:
                illustrative:
                  summary: Finance directors at law firms in Manchester
                  value:
                    audience:
                      schema: mydatahub.audience/1
                      dataset: uk_contacts
                      where:
                        all:
                          - field: contact.title
                            op: matches
                            terms:
                              - finance director
                          - field: company.sic_2007
                            op: in
                            values:
                              - "69102"
                          - field: site.town
                            op: in
                            values:
                              - manchester
                    readback: Finance directors at solicitors (SIC 69102) in Manchester.
                    chips:
                      - label: "Job title: finance director plus expansions"
                        pointer: /where/all/0
                        from_brief: finance directors
                      - label: "Industry: solicitors (SIC 69102)"
                        pointer: /where/all/1
                        from_brief: law firms
                      - label: "Town: Manchester"
                        pointer: /where/all/2
                        from_brief: in Manchester
                    questions: []
                    unrecognised: []
                    quantity: 300
        "400":
          $ref: "#/components/responses/BadRequest"
        "422":
          $ref: "#/components/responses/ValidationFailed"
        "429":
          $ref: "#/components/responses/RateLimited"
        default:
          $ref: "#/components/responses/ServerError"
      x-cost-credits: 0
      x-free: true
      x-anonymous: true
      x-mcp-tool: compile_audience
  /counts:
    post:
      operationId: createCount
      summary: Count an audience
      description: Count an audience, inline or by aud_ id, at contact or company level with up to three breakdown fields. Returns Total, New to you (signed in) and Emailable with companies, breakdowns, quality, the compliance summary, a price estimate and a content-addressed count id (also the ETag). Free, and needs no account; anonymous and trial callers see counts under 100 and every breakdown cell rounded to the nearest 5. Lawful basis for named work emails is UK GDPR Article 6(1)(f) legitimate interests; sole traders and ordinary partnerships are individual subscribers and are left out of email audiences by default.
      tags:
        - Counts
      security:
        - {}
        - OAuth2:
            - counts:read
        - ApiKey:
            - counts:read
      parameters:
        - $ref: "#/components/parameters/MyDataHubVersion"
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/CountRequest"
            examples:
              spec:
                summary: Marketing managers and above at manufacturers within 20 miles of Leeds
                value:
                  audience:
                    schema: mydatahub.audience/1
                    dataset: uk_contacts
                    where:
                      all:
                        - field: contact.title
                          op: matches
                          terms:
                            - marketing manager
                          mode: contains
                        - field: contact.job_level_rank
                          op: in
                          values:
                            - 1
                            - 2
                            - 3
                            - 4
                            - 5
                            - 6
                            - 8
                            - 9
                        - field: company.sic_2007
                          op: in
                          values:
                            - C
                        - field: company.employee_band
                          op: gte
                          value: 50-99
                        - field: site.location
                          op: within_radius
                          centre:
                            place: Leeds
                          radius_miles: 20
                        - not:
                            field: email.address_sha256
                            op: in_list
                            list_id: sup_01JB4H7Q2V7XN3M9R6T1Y5W8ZA
                  level: contact
                  breakdown_by:
                    - contact.job_level_rank
      responses:
        "200":
          description: The count.
          headers:
            RateLimit:
              $ref: "#/components/headers/RateLimit"
            RateLimit-Policy:
              $ref: "#/components/headers/RateLimit-Policy"
            ETag:
              $ref: "#/components/headers/ETag"
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Count"
              examples:
                spec:
                  summary: Marketing managers and above at manufacturers within 20 miles of Leeds
                  value:
                    id: cnt_7K2PQ4MAXN3R6TBY5WDZ
                    dataset: uk_contacts
                    dataset_version: 2026-W39
                    credits_per_record: 1
                    contacts: 12840
                    companies: 3115
                    emailable: 12310
                    new_to_you: null
                    quality:
                      email_coverage_pct: 96.8
                      verified_90d_pct: 88.4
                    breakdowns:
                      - by: contact.job_level_rank
                        cells:
                          - value: director
                            contacts: 4120
                    compliance_summary:
                      lawful_basis: legitimate_interests_art6_1_f
                      corporate_subscriber: 12310
                      individual_subscriber_excluded: 530
                      tps_ctps_excluded: 1120
                      article14_notified: 12290
                      article14_pending_within_window: 20
                      suppressed_removed:
                        - list_id: sup_01JB4H7Q2V7XN3M9R6T1Y5W8ZA
                          removed: 260
                    estimate:
                      credits: 12310
                      gbp_ex_vat:
                        per_contact_scale: 2708.2
                        cheapest: 1249
                    expires_at: "2027-09-26T10:14:00Z"
                    engine_rules_version: 2026.09.1
                    defaults:
                      - id: active_companies
                        applied: true
                        views:
                          - total
                          - new_to_you
                          - emailable
                        removed: 2210
                      - id: named_emails
                        applied: true
                        views:
                          - emailable
                        removed: 410
                      - id: corporate_subscribers
                        applied: true
                        views:
                          - emailable
                        removed: 530
                      - id: no_registered_phones
                        applied: false
                        views:
                          - total
                          - new_to_you
                          - emailable
                        removed: 1120
                        screened_at: "2026-09-25"
                    disclosure:
                      level: rounded
                      rounded_to: 5
                      secondary_suppression: false
                      notes:
                        - Counts under 100 and every breakdown cell are rounded to the nearest 5 for anonymous and trial callers.
                    premium: 8120
                    all_records: 12840
                    premium_note: "Premium at launch: in the premium export of 2026-09-28, this dataset version's as-of date."
        "400":
          $ref: "#/components/responses/BadRequest"
        "401":
          $ref: "#/components/responses/Unauthorized"
        "422":
          $ref: "#/components/responses/ValidationFailed"
        "429":
          $ref: "#/components/responses/RateLimited"
        default:
          $ref: "#/components/responses/ServerError"
      x-cost-credits: 0
      x-free: true
      x-anonymous: true
      x-mcp-tool: count_audience
  /counts/{id}:
    get:
      operationId: getCount
      summary: Get a count
      description: "Replay a count by id for 12 months: the effective definition, the compiled plan (expanded terms, resolved centres, SIC codes, suppression checksums), the pinned dataset and engine rules versions, all three views, breakdowns, the compliance summary, quality, the estimate and the number on the current dataset. The ETag equals the id."
      tags:
        - Counts
      security:
        - {}
        - OAuth2:
            - counts:read
        - ApiKey:
            - counts:read
      parameters:
        - $ref: "#/components/parameters/MyDataHubVersion"
        - name: id
          in: path
          required: true
          description: A count id from POST /v1/counts or count_audience. Either case is accepted; responses use upper case.
          schema:
            type: string
            pattern: ^cnt_[A-Za-z2-7]{20}$
            examples:
              - cnt_7K2PQ4MAXN3R6TBY5WDZ
          example: cnt_7K2PQ4MAXN3R6TBY5WDZ
      responses:
        "200":
          description: The count as it ran.
          headers:
            RateLimit:
              $ref: "#/components/headers/RateLimit"
            RateLimit-Policy:
              $ref: "#/components/headers/RateLimit-Policy"
            ETag:
              $ref: "#/components/headers/ETag"
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/CountReplay"
              examples:
                illustrative:
                  summary: Replay of the Leeds count
                  value:
                    id: cnt_7K2PQ4MAXN3R6TBY5WDZ
                    dataset: uk_contacts
                    dataset_version: 2026-W39
                    credits_per_record: 1
                    contacts: 12840
                    companies: 3115
                    emailable: 12310
                    new_to_you: null
                    quality:
                      email_coverage_pct: 96.8
                      verified_90d_pct: 88.4
                      verified_measured_at: "2026-09-28"
                      deliverability:
                        good: 91.2
                        risky: 6.1
                        poor: 2.7
                      avg_days_since_verification: 41
                    breakdowns:
                      - by: contact.job_level_rank
                        cells:
                          - value: director
                            contacts: 4120
                    compliance_summary:
                      lawful_basis: legitimate_interests_art6_1_f
                      corporate_subscriber: 12310
                      individual_subscriber_excluded: 530
                      tps_ctps_excluded: 1120
                      article14_notified: 12290
                      article14_pending_within_window: 20
                      suppressed_removed:
                        - list_id: sup_01JB4H7Q2V7XN3M9R6T1Y5W8ZA
                          removed: 260
                    estimate:
                      credits: 12310
                      gbp_ex_vat:
                        per_contact_scale: 2708.2
                        cheapest: 1249
                    expires_at: "2027-09-26T10:14:00Z"
                    engine_rules_version: 2026.09.1
                    defaults:
                      - id: active_companies
                        applied: true
                        views:
                          - total
                          - new_to_you
                          - emailable
                        removed: 2210
                      - id: named_emails
                        applied: true
                        views:
                          - emailable
                        removed: 410
                      - id: corporate_subscribers
                        applied: true
                        views:
                          - emailable
                        removed: 530
                      - id: no_registered_phones
                        applied: false
                        views:
                          - total
                          - new_to_you
                          - emailable
                        removed: 1120
                        screened_at: "2026-09-25"
                    disclosure:
                      level: exact
                      rounded_to: null
                      secondary_suppression: false
                      notes: []
                    premium: 8120
                    all_records: 12840
                    premium_note: "Premium at launch: in the premium export of 2026-09-28, this dataset version's as-of date."
                    as_of: "2026-09-28"
                    level: contact
                    views:
                      total:
                        contacts: 12840
                        companies: 3115
                      new_to_you: null
                      emailable:
                        contacts: 12310
                        companies: 3002
                      premium:
                        contacts: 8120
                        companies: 2240
                      all_records:
                        contacts: 12840
                        companies: 3115
                    expanded_terms:
                      - term: Marketing Manager
                        from_term: marketing manager
                        expansion_set: input
                        contacts: 6210
                      - term: Marketing Managers
                        from_term: marketing manager
                        expansion_set: plural_singular
                        contacts: 14
                      - term: Mktg Manager
                        from_term: marketing manager
                        expansion_set: abbreviations
                        contacts: under 10
                    effective_audience:
                      schema: mydatahub.audience/1
                      dataset: uk_contacts
                      dataset_version: 2026-W39
                      where:
                        all:
                          - field: site.location
                            op: within_radius
                            centre:
                              place: Leeds
                            radius_miles: 20
                          - field: contact.title
                            op: matches
                            terms:
                              - marketing manager
                            mode: contains
                            expand:
                              - plural_singular
                              - department_synonyms
                              - ampersand_and
                              - abbreviations
                          - field: company.employee_band
                            op: gte
                            value: 50-99
                          - field: company.sic_2007
                            op: in
                            values:
                              - C
                          - field: company.status
                            op: in
                            values:
                              - active
                          - field: contact.job_level_rank
                            op: in
                            values:
                              - 1
                              - 2
                              - 3
                              - 4
                              - 5
                              - 6
                              - 8
                              - 9
                          - not:
                              field: email.address_sha256
                              op: in_list
                              list_id: sup_01JB4H7Q2V7XN3M9R6T1Y5W8ZA
                      email_policy:
                        min_deliverability_score: 70
                        allow_catch_all: false
                      dedupe: none
                    plan:
                      engine_rules_version: 2026.09.1
                      defaults_applied:
                        - active_companies
                        - named_emails
                        - corporate_subscribers
                      expanded_terms:
                        - term: Marketing Manager
                          from_term: marketing manager
                          expansion_set: input
                          contacts: 6210
                        - term: Marketing Managers
                          from_term: marketing manager
                          expansion_set: plural_singular
                          contacts: 14
                        - term: Mktg Manager
                          from_term: marketing manager
                          expansion_set: abbreviations
                          contacts: under 10
                      centres:
                        - label: Leeds
                          code: "E08000035"
                          lat: 53.7997
                          lon: -1.5492
                          radius_miles: 20
                          postcode_districts_matched: 64
                      sic_codes: []
                      geography_sets: []
                      suppressions:
                        - list_id: sup_01JB4H7Q2V7XN3M9R6T1Y5W8ZA
                          checksum: 9f2c4e0a1b7d3c5e8f6a2b4c6d8e0f1a3b5c7d9e1f2a4b6c8d0e2f4a6b8c0d2e
                          row_count: 1198
                    current:
                      dataset_version: 2026-W40
                      contacts: 12861
                      companies: 3119
                    removed_since: 3
                    created_at: "2026-09-26T10:14:00Z"
        "400":
          $ref: "#/components/responses/BadRequest"
        "401":
          $ref: "#/components/responses/Unauthorized"
        "404":
          $ref: "#/components/responses/NotFound"
        "429":
          $ref: "#/components/responses/RateLimited"
        default:
          $ref: "#/components/responses/ServerError"
      x-cost-credits: 0
      x-free: true
      x-anonymous: true
  /previews:
    post:
      operationId: createPreview
      summary: Preview sample records
      description: Up to 25 masked sample rows from a seed fixed per audience, so a repeat returns the same rows. Each row has verification badges, why it matches and a provenance card with the compliance object. No rows when the count is under 25. Free.
      tags:
        - Counts
      security:
        - {}
        - OAuth2:
            - previews:read
        - ApiKey:
            - previews:read
      parameters:
        - $ref: "#/components/parameters/MyDataHubVersion"
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/PreviewRequest"
            examples:
              illustrative:
                summary: Ten rows of the Leeds count
                value:
                  count_id: cnt_7K2PQ4MAXN3R6TBY5WDZ
                  limit: 10
      responses:
        "200":
          description: The sample.
          headers:
            RateLimit:
              $ref: "#/components/headers/RateLimit"
            RateLimit-Policy:
              $ref: "#/components/headers/RateLimit-Policy"
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Preview"
              examples:
                illustrative:
                  summary: Ten rows of the Leeds count
                  value:
                    id: prv_01JB4H3M7Q2V7XN3M9R6T1Y5W8
                    count_id: cnt_7K2PQ4MAXN3R6TBY5WDZ
                    dataset_version: 2026-W39
                    seed: 7k2pq4ma
                    count: 12840
                    rows:
                      - id: prv_01JB4H3N2V7XN3M9R6T1Y5W8ZA
                        title: Marketing Director
                        job_level: Director
                        job_function: Marketing
                        company:
                          name: Example Manufacturing Ltd
                          companies_house_number: "12345678"
                          status: active
                          employee_band: 100-199
                          sic_2007:
                            - C
                        site:
                          town: Leeds
                          postcode_district: LS11
                          region: yorkshire_and_the_humber
                        email_masked: j••••@••••.co.uk
                        email_type: named
                        phone_masked: 0113 ••• •••1
                        tps_ctps_status: clear
                        badges:
                          - badge: verified
                            date: "2026-09-10"
                            label: Verified 10 September 2026
                        why_it_matches:
                          - pointer: /where/all/0
                            field: contact.title
                            value: Marketing Director
                            explanation: Marketing Director matched marketing manager through expansion.
                          - pointer: /where/all/3
                            field: company.employee_band
                            value: 100-199
                            explanation: Employees 120, inside 50 or more.
                        provenance:
                          sources:
                            title: emailmovers_research
                            company: companies_house
                            email: emailmovers_research
                          first_seen_at: "2026-05-02"
                          last_verified_at: "2026-08-30"
                          method: telephone
                          companies_house_number: "12345678"
                          company_status: active
                          article14_status: notified
                          tps_ctps_screened_at: "2026-09-12"
                          lawful_basis: legitimate_interests_art6_1_f
                        compliance:
                          lawful_basis: legitimate_interests_art6_1_f
                          lia_ref: EM-LIA-2026-03
                          subscriber_class: ltd
                          corporate_subscriber: true
                          email_marketing_basis: pecr_corporate_subscriber
                          subscriber_evidence:
                            companies_house_number: "12345678"
                            company_type: ltd
                          article14_status: notified
                          article14_notified_at: "2026-06-14"
                          notice_version: 2026-02
                          provenance:
                            company:
                              source: companies_house
                              as_of: "2026-09-21"
                            contact:
                              source: emailmovers_research
                              compiled_at: "2026-05-02"
                              last_verified_at: "2026-08-30"
                              method: telephone
                            email:
                              verified_at: "2026-09-10"
                              result: ok
                              provider_location: EU
                          tps_ctps_status: clear
                          tps_ctps_screened_at: "2026-09-12"
                          tps_ctps_valid_until: "2026-10-10"
                          suppression_status: none
                          record_age_days: 147
                          attribution: Contains public sector information licensed under the Open Government Licence v3.0
                    withheld: null
                    masked: true
                    watermark: null
                    created_at: "2026-09-26T10:15:00Z"
                    disclosure:
                      level: rounded
                      rounded_to: 5
                      secondary_suppression: false
                      notes:
                        - Counts under 100 and every breakdown cell are rounded to the nearest 5 for anonymous and trial callers.
        "400":
          $ref: "#/components/responses/BadRequest"
        "401":
          $ref: "#/components/responses/Unauthorized"
        "422":
          $ref: "#/components/responses/ValidationFailed"
        "429":
          $ref: "#/components/responses/RateLimited"
        default:
          $ref: "#/components/responses/ServerError"
      x-cost-credits: 0
      x-free: true
      x-anonymous: true
      x-mcp-tool: preview_records
  /sample-requests:
    post:
      operationId: createSampleRequest
      summary: Ask for a sample of a count
      description: "Starts a sample of about 10 records of a counted audience: the person then confirms a work email address and downloads it once. The count must allow a sample under the caller's rules (25 records or more, not one company). Never returns records."
      tags:
        - Samples
      security:
        - OAuth2:
            - samples:request
        - ApiKey:
            - samples:request
      parameters:
        - $ref: "#/components/parameters/MyDataHubVersion"
        - $ref: "#/components/parameters/IdempotencyKey"
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/SampleRequestCreate"
            examples:
              illustrative:
                summary: A sample of the Leeds count
                value:
                  count_id: cnt_7K2PQ4MAXN3R6TBY5WDZ
      responses:
        "201":
          description: The request, waiting for the address.
          headers:
            RateLimit:
              $ref: "#/components/headers/RateLimit"
            RateLimit-Policy:
              $ref: "#/components/headers/RateLimit-Policy"
            Location:
              $ref: "#/components/headers/Location"
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/SampleRequest"
              examples:
                illustrative:
                  summary: A sample of the Leeds count
                  value:
                    id: smr_01JB4P2Q5W8ZAK8Q2V7XN3M9R6
                    count_id: cnt_7K2PQ4MAXN3R6TBY5WDZ
                    status: awaiting_email
                    created_at: "2026-10-11T10:00:00Z"
                    expires_at: "2026-10-12T10:00:00Z"
        "400":
          $ref: "#/components/responses/BadRequest"
        "401":
          $ref: "#/components/responses/Unauthorized"
        "403":
          $ref: "#/components/responses/Forbidden"
        "404":
          $ref: "#/components/responses/NotFound"
        "409":
          $ref: "#/components/responses/Conflict"
        "422":
          $ref: "#/components/responses/ValidationFailed"
        "429":
          $ref: "#/components/responses/RateLimited"
        default:
          $ref: "#/components/responses/ServerError"
      x-cost-credits: 0
      x-free: true
      x-anonymous: false
      x-idempotency-key: optional
  /sample-requests/{id}/email:
    post:
      operationId: createSampleRequestEmail
      summary: Send the confirmation link for a sample
      description: The person's work email address, from the calling server's own form, never through a model. A one-time link is emailed; the answer is the same whether the address was accepted or refused (suppressed, objecting, over a limit), so it cannot be used to test addresses. Free webmail and malformed addresses get 422.
      tags:
        - Samples
      security:
        - OAuth2:
            - samples:request
        - ApiKey:
            - samples:request
      parameters:
        - $ref: "#/components/parameters/MyDataHubVersion"
        - name: id
          in: path
          required: true
          description: "Format: a sample request id: smr_ followed by a 26 character ULID in upper case Crockford base32, such as smr_01JB4P2Q5W8ZAK8Q2V7XN3M9R6."
          schema:
            type: string
            pattern: ^smr_[0-7][0-9A-HJKMNP-TV-Z]{25}$
            examples:
              - smr_01JB4P2Q5W8ZAK8Q2V7XN3M9R6
          example: smr_01JB4P2Q5W8ZAK8Q2V7XN3M9R6
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/SampleRequestEmail"
            examples:
              illustrative:
                summary: The address from the form
                value:
                  email: jordan.hale@example.co.uk
                  follow_up: false
                  wording: "2026-10-11"
      responses:
        "202":
          description: "Taken: the person checks their inbox."
          headers:
            RateLimit:
              $ref: "#/components/headers/RateLimit"
            RateLimit-Policy:
              $ref: "#/components/headers/RateLimit-Policy"
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/SampleRequestEmailAccepted"
              examples:
                illustrative:
                  summary: The address from the form
                  value:
                    status: check_your_inbox
        "400":
          $ref: "#/components/responses/BadRequest"
        "401":
          $ref: "#/components/responses/Unauthorized"
        "403":
          $ref: "#/components/responses/Forbidden"
        "404":
          $ref: "#/components/responses/NotFound"
        "422":
          $ref: "#/components/responses/ValidationFailed"
        "429":
          $ref: "#/components/responses/RateLimited"
        default:
          $ref: "#/components/responses/ServerError"
      x-cost-credits: 0
      x-free: true
      x-anonymous: false
  /sample-requests/{id}:
    get:
      operationId: getSampleRequest
      summary: Get a sample request
      description: "The request's status only: never the address or the records."
      tags:
        - Samples
      security:
        - OAuth2:
            - samples:request
        - ApiKey:
            - samples:request
      parameters:
        - $ref: "#/components/parameters/MyDataHubVersion"
        - name: id
          in: path
          required: true
          description: "Format: a sample request id: smr_ followed by a 26 character ULID in upper case Crockford base32, such as smr_01JB4P2Q5W8ZAK8Q2V7XN3M9R6."
          schema:
            type: string
            pattern: ^smr_[0-7][0-9A-HJKMNP-TV-Z]{25}$
            examples:
              - smr_01JB4P2Q5W8ZAK8Q2V7XN3M9R6
          example: smr_01JB4P2Q5W8ZAK8Q2V7XN3M9R6
      responses:
        "200":
          description: The request.
          headers:
            RateLimit:
              $ref: "#/components/headers/RateLimit"
            RateLimit-Policy:
              $ref: "#/components/headers/RateLimit-Policy"
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/SampleRequest"
              examples:
                illustrative:
                  summary: Waiting for the person to confirm
                  value:
                    id: smr_01JB4P2Q5W8ZAK8Q2V7XN3M9R6
                    count_id: cnt_7K2PQ4MAXN3R6TBY5WDZ
                    status: email_sent
                    created_at: "2026-10-11T10:00:00Z"
                    expires_at: "2026-10-12T10:00:00Z"
        "400":
          $ref: "#/components/responses/BadRequest"
        "401":
          $ref: "#/components/responses/Unauthorized"
        "403":
          $ref: "#/components/responses/Forbidden"
        "404":
          $ref: "#/components/responses/NotFound"
        "429":
          $ref: "#/components/responses/RateLimited"
        default:
          $ref: "#/components/responses/ServerError"
      x-cost-credits: 0
      x-free: true
      x-anonymous: false
  /sample-downloads:
    post:
      operationId: createSampleDownload
      summary: Confirm a sample and get its download link
      description: Sent by the confirmation page when the person presses its button, with the token from the emailed link; the token is the credential, so no key or session is needed. The first press confirms the address and chooses up to 10 records (verified named emails with an Article 14 notice, none at the person's own domain, at most 2 a company), read afresh against every suppression. Each press until the download gives a new single-use link for 5 minutes; after it, the answer says the sample was already downloaded. An unknown or expired token gets 404.
      tags:
        - Samples
      security: []
      parameters:
        - $ref: "#/components/parameters/MyDataHubVersion"
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/SampleDownloadCreate"
            examples:
              illustrative:
                summary: The person presses Download your sample
                value:
                  token: q3V8xN2fK7mR1tY6pL0sD4hJ9wZ5cB8eA2gU7iO3nQ1
      responses:
        "200":
          description: The sample's link, or why there is none.
          headers:
            RateLimit:
              $ref: "#/components/headers/RateLimit"
            RateLimit-Policy:
              $ref: "#/components/headers/RateLimit-Policy"
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/SampleDownload"
              examples:
                illustrative:
                  summary: The person presses Download your sample
                  value:
                    status: ready
                    records: 10
                    download_url: /files/samples/sdl_Xr4mP8qT2vN6kJ1wL9sF3hD7bG5cZ0yA8eU2iO6nQ4t
                    expires_at: "2026-10-11T10:20:00Z"
        "400":
          $ref: "#/components/responses/BadRequest"
        "404":
          $ref: "#/components/responses/NotFound"
        "422":
          $ref: "#/components/responses/ValidationFailed"
        "429":
          $ref: "#/components/responses/RateLimited"
        default:
          $ref: "#/components/responses/ServerError"
      x-cost-credits: 0
      x-free: true
      x-anonymous: true
  /audiences:
    get:
      operationId: listAudiences
      summary: List saved audiences
      description: Saved audiences shared across the organisation, with current counts and last refresh.
      tags:
        - Audiences
      security:
        - OAuth2:
            - audiences:read
        - ApiKey:
            - audiences:read
      parameters:
        - $ref: "#/components/parameters/MyDataHubVersion"
        - name: q
          in: query
          required: false
          description: Filter by name.
          schema:
            type: string
            maxLength: 100
        - name: limit
          in: query
          required: false
          description: Items per page, 1 to 100. Default 25.
          schema:
            default: 25
            type: integer
            minimum: 1
            maximum: 100
        - name: cursor
          in: query
          required: false
          description: An opaque cursor from next_cursor. Pass it back unchanged.
          schema:
            type: string
            minLength: 1
            maxLength: 512
      responses:
        "200":
          description: A page of saved audiences.
          headers:
            RateLimit:
              $ref: "#/components/headers/RateLimit"
            RateLimit-Policy:
              $ref: "#/components/headers/RateLimit-Policy"
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/SavedAudienceList"
              examples:
                illustrative:
                  summary: One saved audience
                  value:
                    data:
                      - id: aud_01JB4H2K9V6XN3M9R6T1Y5W8ZA
                        name: Marketing directors, Leeds
                        audience:
                          schema: mydatahub.audience/1
                          dataset: uk_contacts
                          dataset_version: 2026-W39
                          where:
                            all:
                              - field: contact.title
                                op: matches
                                terms:
                                  - marketing manager
                                mode: contains
                                expand:
                                  - plural_singular
                                  - department_synonyms
                                  - ampersand_and
                                  - abbreviations
                              - field: contact.job_level_rank
                                op: in
                                values:
                                  - 1
                                  - 2
                                  - 3
                                  - 4
                                  - 5
                                  - 6
                                  - 8
                                  - 9
                              - field: company.sic_2007
                                op: in
                                values:
                                  - C
                              - field: company.employee_band
                                op: gte
                                value: 50-99
                              - field: site.location
                                op: within_radius
                                centre:
                                  place: Leeds
                                radius_miles: 20
                              - not:
                                  field: email.address_sha256
                                  op: in_list
                                  list_id: sup_01JB4H7Q2V7XN3M9R6T1Y5W8ZA
                          email_policy:
                            min_deliverability_score: 70
                            allow_catch_all: false
                          dedupe: none
                        brief: Marketing managers and above at manufacturers within 20 miles of Leeds
                        current_count:
                          count_id: cnt_7K2PQ4MAXN3R6TBY5WDZ
                          dataset_version: 2026-W39
                          contacts: 12840
                          companies: 3115
                          emailable: 12310
                          counted_at: "2026-09-26T10:14:00Z"
                        last_refreshed_at: "2026-09-28T06:00:00Z"
                        created_at: "2026-09-26T10:20:00Z"
                        updated_at: "2026-09-26T10:20:00Z"
                        created_by: usr_01JA0Q3V7XN3M9R6T1Y5W8ZAK8
                        url: https://mydatahub.emailmovers.com/audiences/aud_01JB4H2K9V6XN3M9R6T1Y5W8ZA
                    has_more: false
                    next_cursor: null
        "400":
          $ref: "#/components/responses/BadRequest"
        "401":
          $ref: "#/components/responses/Unauthorized"
        "403":
          $ref: "#/components/responses/Forbidden"
        "422":
          $ref: "#/components/responses/ValidationFailed"
        "429":
          $ref: "#/components/responses/RateLimited"
        default:
          $ref: "#/components/responses/ServerError"
      x-cost-credits: 0
      x-free: true
      x-anonymous: false
      x-mcp-tool: list_audiences
    post:
      operationId: createAudience
      summary: Save an audience
      description: Save a named audience definition, shared across the organisation. Saving is free; it is counted at once and on every refresh.
      tags:
        - Audiences
      security:
        - OAuth2:
            - audiences:write
        - ApiKey:
            - audiences:write
      parameters:
        - $ref: "#/components/parameters/MyDataHubVersion"
        - $ref: "#/components/parameters/IdempotencyKey"
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/SavedAudienceRequest"
            examples:
              illustrative:
                summary: Save the Leeds audience
                value:
                  name: Marketing directors, Leeds
                  audience:
                    schema: mydatahub.audience/1
                    dataset: uk_contacts
                    dataset_version: 2026-W39
                    where:
                      all:
                        - field: contact.title
                          op: matches
                          terms:
                            - marketing manager
                          mode: contains
                          expand:
                            - plural_singular
                            - department_synonyms
                            - ampersand_and
                            - abbreviations
                        - field: contact.job_level_rank
                          op: in
                          values:
                            - 1
                            - 2
                            - 3
                            - 4
                            - 5
                            - 6
                            - 8
                            - 9
                        - field: company.sic_2007
                          op: in
                          values:
                            - C
                        - field: company.employee_band
                          op: gte
                          value: 50-99
                        - field: site.location
                          op: within_radius
                          centre:
                            place: Leeds
                          radius_miles: 20
                        - not:
                            field: email.address_sha256
                            op: in_list
                            list_id: sup_01JB4H7Q2V7XN3M9R6T1Y5W8ZA
                    email_policy:
                      min_deliverability_score: 70
                      allow_catch_all: false
                    dedupe: none
      responses:
        "201":
          description: The saved audience.
          headers:
            RateLimit:
              $ref: "#/components/headers/RateLimit"
            RateLimit-Policy:
              $ref: "#/components/headers/RateLimit-Policy"
            Location:
              $ref: "#/components/headers/Location"
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/SavedAudience"
              examples:
                illustrative:
                  summary: Save the Leeds audience
                  value:
                    id: aud_01JB4H2K9V6XN3M9R6T1Y5W8ZA
                    name: Marketing directors, Leeds
                    audience:
                      schema: mydatahub.audience/1
                      dataset: uk_contacts
                      dataset_version: 2026-W39
                      where:
                        all:
                          - field: contact.title
                            op: matches
                            terms:
                              - marketing manager
                            mode: contains
                            expand:
                              - plural_singular
                              - department_synonyms
                              - ampersand_and
                              - abbreviations
                          - field: contact.job_level_rank
                            op: in
                            values:
                              - 1
                              - 2
                              - 3
                              - 4
                              - 5
                              - 6
                              - 8
                              - 9
                          - field: company.sic_2007
                            op: in
                            values:
                              - C
                          - field: company.employee_band
                            op: gte
                            value: 50-99
                          - field: site.location
                            op: within_radius
                            centre:
                              place: Leeds
                            radius_miles: 20
                          - not:
                              field: email.address_sha256
                              op: in_list
                              list_id: sup_01JB4H7Q2V7XN3M9R6T1Y5W8ZA
                      email_policy:
                        min_deliverability_score: 70
                        allow_catch_all: false
                      dedupe: none
                    brief: Marketing managers and above at manufacturers within 20 miles of Leeds
                    current_count:
                      count_id: cnt_7K2PQ4MAXN3R6TBY5WDZ
                      dataset_version: 2026-W39
                      contacts: 12840
                      companies: 3115
                      emailable: 12310
                      counted_at: "2026-09-26T10:14:00Z"
                    last_refreshed_at: "2026-09-28T06:00:00Z"
                    created_at: "2026-09-26T10:20:00Z"
                    updated_at: "2026-09-26T10:20:00Z"
                    created_by: usr_01JA0Q3V7XN3M9R6T1Y5W8ZAK8
                    url: https://mydatahub.emailmovers.com/audiences/aud_01JB4H2K9V6XN3M9R6T1Y5W8ZA
        "400":
          $ref: "#/components/responses/BadRequest"
        "401":
          $ref: "#/components/responses/Unauthorized"
        "403":
          $ref: "#/components/responses/Forbidden"
        "409":
          $ref: "#/components/responses/Conflict"
        "422":
          $ref: "#/components/responses/ValidationFailed"
        "429":
          $ref: "#/components/responses/RateLimited"
        default:
          $ref: "#/components/responses/ServerError"
      x-cost-credits: 0
      x-free: true
      x-anonymous: false
      x-idempotency-key: optional
      x-mcp-tool: save_audience
  /audiences/{id}:
    get:
      operationId: getAudience
      summary: Get a saved audience
      description: One saved audience with its definition and current count.
      tags:
        - Audiences
      security:
        - OAuth2:
            - audiences:read
        - ApiKey:
            - audiences:read
      parameters:
        - $ref: "#/components/parameters/MyDataHubVersion"
        - name: id
          in: path
          required: true
          description: "Format: an audience id: aud_ followed by a 26 character ULID in upper case Crockford base32, such as aud_01JB4H2K9V6XN3M9R6T1Y5W8ZA."
          schema:
            type: string
            pattern: ^aud_[0-7][0-9A-HJKMNP-TV-Z]{25}$
            examples:
              - aud_01JB4H2K9V6XN3M9R6T1Y5W8ZA
          example: aud_01JB4H2K9V6XN3M9R6T1Y5W8ZA
      responses:
        "200":
          description: The saved audience.
          headers:
            RateLimit:
              $ref: "#/components/headers/RateLimit"
            RateLimit-Policy:
              $ref: "#/components/headers/RateLimit-Policy"
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/SavedAudience"
              examples:
                illustrative:
                  summary: The Leeds audience
                  value:
                    id: aud_01JB4H2K9V6XN3M9R6T1Y5W8ZA
                    name: Marketing directors, Leeds
                    audience:
                      schema: mydatahub.audience/1
                      dataset: uk_contacts
                      dataset_version: 2026-W39
                      where:
                        all:
                          - field: contact.title
                            op: matches
                            terms:
                              - marketing manager
                            mode: contains
                            expand:
                              - plural_singular
                              - department_synonyms
                              - ampersand_and
                              - abbreviations
                          - field: contact.job_level_rank
                            op: in
                            values:
                              - 1
                              - 2
                              - 3
                              - 4
                              - 5
                              - 6
                              - 8
                              - 9
                          - field: company.sic_2007
                            op: in
                            values:
                              - C
                          - field: company.employee_band
                            op: gte
                            value: 50-99
                          - field: site.location
                            op: within_radius
                            centre:
                              place: Leeds
                            radius_miles: 20
                          - not:
                              field: email.address_sha256
                              op: in_list
                              list_id: sup_01JB4H7Q2V7XN3M9R6T1Y5W8ZA
                      email_policy:
                        min_deliverability_score: 70
                        allow_catch_all: false
                      dedupe: none
                    brief: Marketing managers and above at manufacturers within 20 miles of Leeds
                    current_count:
                      count_id: cnt_7K2PQ4MAXN3R6TBY5WDZ
                      dataset_version: 2026-W39
                      contacts: 12840
                      companies: 3115
                      emailable: 12310
                      counted_at: "2026-09-26T10:14:00Z"
                    last_refreshed_at: "2026-09-28T06:00:00Z"
                    created_at: "2026-09-26T10:20:00Z"
                    updated_at: "2026-09-26T10:20:00Z"
                    created_by: usr_01JA0Q3V7XN3M9R6T1Y5W8ZAK8
                    url: https://mydatahub.emailmovers.com/audiences/aud_01JB4H2K9V6XN3M9R6T1Y5W8ZA
        "400":
          $ref: "#/components/responses/BadRequest"
        "401":
          $ref: "#/components/responses/Unauthorized"
        "403":
          $ref: "#/components/responses/Forbidden"
        "404":
          $ref: "#/components/responses/NotFound"
        "429":
          $ref: "#/components/responses/RateLimited"
        default:
          $ref: "#/components/responses/ServerError"
      x-cost-credits: 0
      x-free: true
      x-anonymous: false
    patch:
      operationId: updateAudience
      summary: Update a saved audience
      description: Rename a saved audience or replace its definition. The next count uses the new definition.
      tags:
        - Audiences
      security:
        - OAuth2:
            - audiences:write
        - ApiKey:
            - audiences:write
      parameters:
        - $ref: "#/components/parameters/MyDataHubVersion"
        - name: id
          in: path
          required: true
          description: "Format: an audience id: aud_ followed by a 26 character ULID in upper case Crockford base32, such as aud_01JB4H2K9V6XN3M9R6T1Y5W8ZA."
          schema:
            type: string
            pattern: ^aud_[0-7][0-9A-HJKMNP-TV-Z]{25}$
            examples:
              - aud_01JB4H2K9V6XN3M9R6T1Y5W8ZA
          example: aud_01JB4H2K9V6XN3M9R6T1Y5W8ZA
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/SavedAudienceUpdate"
            examples:
              illustrative:
                summary: Rename the Leeds audience
                value:
                  name: Marketing directors, Leeds
      responses:
        "200":
          description: The updated audience.
          headers:
            RateLimit:
              $ref: "#/components/headers/RateLimit"
            RateLimit-Policy:
              $ref: "#/components/headers/RateLimit-Policy"
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/SavedAudience"
              examples:
                illustrative:
                  summary: Rename the Leeds audience
                  value:
                    id: aud_01JB4H2K9V6XN3M9R6T1Y5W8ZA
                    name: Marketing directors, Leeds
                    audience:
                      schema: mydatahub.audience/1
                      dataset: uk_contacts
                      dataset_version: 2026-W39
                      where:
                        all:
                          - field: contact.title
                            op: matches
                            terms:
                              - marketing manager
                            mode: contains
                            expand:
                              - plural_singular
                              - department_synonyms
                              - ampersand_and
                              - abbreviations
                          - field: contact.job_level_rank
                            op: in
                            values:
                              - 1
                              - 2
                              - 3
                              - 4
                              - 5
                              - 6
                              - 8
                              - 9
                          - field: company.sic_2007
                            op: in
                            values:
                              - C
                          - field: company.employee_band
                            op: gte
                            value: 50-99
                          - field: site.location
                            op: within_radius
                            centre:
                              place: Leeds
                            radius_miles: 20
                          - not:
                              field: email.address_sha256
                              op: in_list
                              list_id: sup_01JB4H7Q2V7XN3M9R6T1Y5W8ZA
                      email_policy:
                        min_deliverability_score: 70
                        allow_catch_all: false
                      dedupe: none
                    brief: Marketing managers and above at manufacturers within 20 miles of Leeds
                    current_count:
                      count_id: cnt_7K2PQ4MAXN3R6TBY5WDZ
                      dataset_version: 2026-W39
                      contacts: 12840
                      companies: 3115
                      emailable: 12310
                      counted_at: "2026-09-26T10:14:00Z"
                    last_refreshed_at: "2026-09-28T06:00:00Z"
                    created_at: "2026-09-26T10:20:00Z"
                    updated_at: "2026-09-26T10:20:00Z"
                    created_by: usr_01JA0Q3V7XN3M9R6T1Y5W8ZAK8
                    url: https://mydatahub.emailmovers.com/audiences/aud_01JB4H2K9V6XN3M9R6T1Y5W8ZA
        "400":
          $ref: "#/components/responses/BadRequest"
        "401":
          $ref: "#/components/responses/Unauthorized"
        "403":
          $ref: "#/components/responses/Forbidden"
        "404":
          $ref: "#/components/responses/NotFound"
        "422":
          $ref: "#/components/responses/ValidationFailed"
        "429":
          $ref: "#/components/responses/RateLimited"
        default:
          $ref: "#/components/responses/ServerError"
      x-cost-credits: 0
      x-free: true
      x-anonymous: false
      x-mcp-tool: save_audience
    delete:
      operationId: deleteAudience
      summary: Delete a saved audience
      description: Delete a saved audience. Counts, quotes and orders made from it keep their own ids and still replay.
      tags:
        - Audiences
      security:
        - OAuth2:
            - audiences:write
        - ApiKey:
            - audiences:write
      parameters:
        - $ref: "#/components/parameters/MyDataHubVersion"
        - name: id
          in: path
          required: true
          description: "Format: an audience id: aud_ followed by a 26 character ULID in upper case Crockford base32, such as aud_01JB4H2K9V6XN3M9R6T1Y5W8ZA."
          schema:
            type: string
            pattern: ^aud_[0-7][0-9A-HJKMNP-TV-Z]{25}$
            examples:
              - aud_01JB4H2K9V6XN3M9R6T1Y5W8ZA
          example: aud_01JB4H2K9V6XN3M9R6T1Y5W8ZA
      responses:
        "204":
          description: Deleted.
          headers:
            RateLimit:
              $ref: "#/components/headers/RateLimit"
            RateLimit-Policy:
              $ref: "#/components/headers/RateLimit-Policy"
        "400":
          $ref: "#/components/responses/BadRequest"
        "401":
          $ref: "#/components/responses/Unauthorized"
        "403":
          $ref: "#/components/responses/Forbidden"
        "404":
          $ref: "#/components/responses/NotFound"
        "429":
          $ref: "#/components/responses/RateLimited"
        default:
          $ref: "#/components/responses/ServerError"
      x-cost-credits: 0
      x-free: true
      x-anonymous: false
  /audiences/{id}/refresh:
    post:
      operationId: refreshAudience
      summary: Refresh a saved audience
      description: Recount a saved audience on the current dataset version and return the difference. The audience.refreshed webhook carries the same object. Free.
      tags:
        - Audiences
      security:
        - OAuth2:
            - audiences:write
        - ApiKey:
            - audiences:write
      parameters:
        - $ref: "#/components/parameters/MyDataHubVersion"
        - name: id
          in: path
          required: true
          description: "Format: an audience id: aud_ followed by a 26 character ULID in upper case Crockford base32, such as aud_01JB4H2K9V6XN3M9R6T1Y5W8ZA."
          schema:
            type: string
            pattern: ^aud_[0-7][0-9A-HJKMNP-TV-Z]{25}$
            examples:
              - aud_01JB4H2K9V6XN3M9R6T1Y5W8ZA
          example: aud_01JB4H2K9V6XN3M9R6T1Y5W8ZA
      responses:
        "200":
          description: The refresh and its diff.
          headers:
            RateLimit:
              $ref: "#/components/headers/RateLimit"
            RateLimit-Policy:
              $ref: "#/components/headers/RateLimit-Policy"
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/AudienceRefresh"
              examples:
                illustrative:
                  summary: Refresh the Leeds audience
                  value:
                    audience_id: aud_01JB4H2K9V6XN3M9R6T1Y5W8ZA
                    previous:
                      count_id: cnt_7K2PQ4MAXN3R6TBY5WDZ
                      dataset_version: 2026-W39
                      contacts: 12840
                      companies: 3115
                      emailable: 12310
                      counted_at: "2026-09-26T10:14:00Z"
                    current:
                      count_id: cnt_Q3N5X2M7K4P6R2T4V4W6
                      dataset_version: 2026-W40
                      contacts: 13054
                      companies: 3160
                      emailable: 12520
                      counted_at: "2026-10-05T06:00:00Z"
                    diff:
                      contacts: 214
                      companies: 45
                      emailable: 210
                      new_records: 231
                      removed_records: 17
        "400":
          $ref: "#/components/responses/BadRequest"
        "401":
          $ref: "#/components/responses/Unauthorized"
        "403":
          $ref: "#/components/responses/Forbidden"
        "404":
          $ref: "#/components/responses/NotFound"
        "429":
          $ref: "#/components/responses/RateLimited"
        default:
          $ref: "#/components/responses/ServerError"
      x-cost-credits: 0
      x-free: true
      x-anonymous: false
  /quotes:
    post:
      operationId: createQuote
      summary: Quote an order
      description: "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."
      tags:
        - Quotes
      security:
        - {}
        - OAuth2:
            - quotes:read
        - ApiKey:
            - quotes:read
      parameters:
        - $ref: "#/components/parameters/MyDataHubVersion"
        - $ref: "#/components/parameters/IdempotencyKey"
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/QuoteRequest"
            examples:
              spec:
                summary: 5,000 of the Leeds records on the standard field set
                value:
                  count_id: cnt_7K2PQ4MAXN3R6TBY5WDZ
                  field_set: standard
                  limit: 5000
                  dedupe_against: previous_orders
                  licence: multi_use_12m
      responses:
        "201":
          description: The machine quote.
          headers:
            RateLimit:
              $ref: "#/components/headers/RateLimit"
            RateLimit-Policy:
              $ref: "#/components/headers/RateLimit-Policy"
            Location:
              $ref: "#/components/headers/Location"
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/MachineQuote"
              examples:
                spec:
                  summary: 5,000 of the Leeds records on the standard field set
                  value:
                    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
        "400":
          $ref: "#/components/responses/BadRequest"
        "401":
          $ref: "#/components/responses/Unauthorized"
        "409":
          $ref: "#/components/responses/Conflict"
        "422":
          $ref: "#/components/responses/ValidationFailed"
        "429":
          $ref: "#/components/responses/RateLimited"
        default:
          $ref: "#/components/responses/ServerError"
      x-cost-credits: 0
      x-free: true
      x-anonymous: true
      x-idempotency-key: optional
      x-mcp-tool: quote_order
  /quotes/{id}:
    get:
      operationId: getQuote
      summary: Get a quote
      description: 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.
      tags:
        - Quotes
      security:
        - {}
        - OAuth2:
            - quotes:read
        - ApiKey:
            - quotes:read
      parameters:
        - $ref: "#/components/parameters/MyDataHubVersion"
        - name: id
          in: path
          required: true
          description: "Format: a quote id: qte_ followed by a 26 character ULID in upper case Crockford base32, such as qte_01JB4M2R6T1Y5W8ZAK8Q2V7XN3."
          schema:
            type: string
            pattern: ^qte_[0-7][0-9A-HJKMNP-TV-Z]{25}$
            examples:
              - qte_01JB4M2R6T1Y5W8ZAK8Q2V7XN3
          example: qte_01JB4P2V7XN3M9R6T1Y5W8ZAK8
      responses:
        "200":
          description: The quote.
          headers:
            RateLimit:
              $ref: "#/components/headers/RateLimit"
            RateLimit-Policy:
              $ref: "#/components/headers/RateLimit-Policy"
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Quote"
              examples:
                illustrative:
                  summary: A human sales quote
                  value:
                    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
        "400":
          $ref: "#/components/responses/BadRequest"
        "401":
          $ref: "#/components/responses/Unauthorized"
        "404":
          $ref: "#/components/responses/NotFound"
        "429":
          $ref: "#/components/responses/RateLimited"
        default:
          $ref: "#/components/responses/ServerError"
      x-cost-credits: 0
      x-free: true
      x-anonymous: true
  /orders:
    post:
      operationId: createOrder
      summary: Create an order (spends credits)
      description: "Spend credits against a live machine quote: 1 credit per complete UK record. Needs the quote_id plus a human confirmation (MCP elicitation, the confirmation_token from the approval page, or a person ordering in the app itself) or a pre-authorised agent key within its per-order cap and monthly budget; a model-set confirmed flag is never consent. A standard API key or an OAuth token with no confirmation gets 403 confirmation-required carrying the approval_url, and nothing is charged. Idempotent on quote_id plus the Idempotency-Key header, so a retry never double charges. Returns 402 with the machine quote and a top_up_url when the balance is short, 403 agent-budget-exceeded when a key's limits do not cover it, 403 business-details-required when an account made by self-serve sign-up has not yet confirmed its business name and that it is a UK business (PATCH /v1/account), and 403 licence-acceptance-required, with the approval_url, when the Data Licence and Sharing Agreement in force was not accepted (licence_accepted_version from a person in the app, the approval page's token, or the agent key's own acceptance). Lawful basis for named work emails is UK GDPR Article 6(1)(f) legitimate interests; sole traders and ordinary partnerships are individual subscribers and are left out of email audiences by default."
      tags:
        - Orders
      security:
        - OAuth2:
            - orders:write
        - ApiKey:
            - orders:write
      parameters:
        - $ref: "#/components/parameters/MyDataHubVersion"
        - $ref: "#/components/parameters/IdempotencyKeyRequired"
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/OrderRequest"
            examples:
              spec:
                summary: Order the quoted 5,000 records
                value:
                  quote_id: qte_01JB4M2R6T1Y5W8ZAK8Q2V7XN3
                  name: Leeds marketing Q4
                  delivery:
                    - api
                    - csv
      responses:
        "202":
          description: The order, queued.
          headers:
            RateLimit:
              $ref: "#/components/headers/RateLimit"
            RateLimit-Policy:
              $ref: "#/components/headers/RateLimit-Policy"
            Location:
              $ref: "#/components/headers/Location"
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Order"
              examples:
                spec:
                  summary: Order the quoted 5,000 records
                  value:
                    id: ord_01JB4N9S3W2X6Y7Z8A1B2C3D4E
                    status: queued
                    credits_charged: 5000
                    records: 5000
                    confirmed_by:
                      method: agent_key
                      key_id: key_01JB3Z4Q2V7XN3M9R6T1Y5W8ZA
                      client_id: https://claude.ai/oauth/claude-code-client-metadata
                    records_url: /v1/orders/ord_01JB4N9S3W2X6Y7Z8A1B2C3D4E/records
                    quote_id: qte_01JB4M2R6T1Y5W8ZAK8Q2V7XN3
                    count_id: cnt_7K2PQ4MAXN3R6TBY5WDZ
                    credits_remaining: 1200
                    cost_gbp: 1495
                    licence_expires_at: "2027-09-26"
                    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
        "400":
          $ref: "#/components/responses/BadRequest"
        "401":
          $ref: "#/components/responses/Unauthorized"
        "402":
          $ref: "#/components/responses/InsufficientCredits"
        "403":
          $ref: "#/components/responses/Forbidden"
        "409":
          $ref: "#/components/responses/Conflict"
        "422":
          $ref: "#/components/responses/ValidationFailed"
        "429":
          $ref: "#/components/responses/RateLimited"
        default:
          $ref: "#/components/responses/ServerError"
      x-cost-credits: 1
      x-cost-unit: record
      x-cost-note: 1 credit per complete UK record (uk_contacts). Suppressed, duplicate and already held records cost 0.
      x-free: false
      x-anonymous: false
      x-idempotency-key: required
      x-mcp-tool: create_order
    get:
      operationId: listOrders
      summary: List orders
      description: Recent orders with status, counts and links, newest first.
      tags:
        - Orders
      security:
        - OAuth2:
            - orders:read
        - ApiKey:
            - orders:read
      parameters:
        - $ref: "#/components/parameters/MyDataHubVersion"
        - name: status
          in: query
          required: false
          description: queued, building, verifying, ready; failed if processing failed; cancelled (only while queued).
          schema:
            type: string
            enum:
              - queued
              - building
              - verifying
              - ready
              - failed
              - cancelled
        - name: limit
          in: query
          required: false
          description: Items per page, 1 to 100. Default 25.
          schema:
            default: 25
            type: integer
            minimum: 1
            maximum: 100
        - name: cursor
          in: query
          required: false
          description: An opaque cursor from next_cursor. Pass it back unchanged.
          schema:
            type: string
            minLength: 1
            maxLength: 512
      responses:
        "200":
          description: A page of orders.
          headers:
            RateLimit:
              $ref: "#/components/headers/RateLimit"
            RateLimit-Policy:
              $ref: "#/components/headers/RateLimit-Policy"
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/OrderList"
              examples:
                illustrative:
                  summary: Recent orders
                  value:
                    data:
                      - id: ord_01JB4N9S3W2X6Y7Z8A1B2C3D4E
                        status: ready
                        credits_charged: 5000
                        records: 5000
                        confirmed_by:
                          method: agent_key
                          key_id: key_01JB3Z4Q2V7XN3M9R6T1Y5W8ZA
                          client_id: https://claude.ai/oauth/claude-code-client-metadata
                          user_agent: claude-code/2.1
                          mtls_certificate_fingerprint: null
                          web_bot_auth_signature_agent: null
                          confirmed_at: "2026-09-26T10:16:00Z"
                        records_url: /v1/orders/ord_01JB4N9S3W2X6Y7Z8A1B2C3D4E/records
                        quote_id: qte_01JB4M2R6T1Y5W8ZAK8Q2V7XN3
                        count_id: cnt_7K2PQ4MAXN3R6TBY5WDZ
                        credits_remaining: 1200
                        cost_gbp: 1495
                        licence_expires_at: "2027-09-26"
                        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
                        name: Leeds marketing Q4
                        dataset_version: 2026-W39
                        field_set: standard
                        delivery:
                          - api
                          - csv
                        verification:
                          checked: 5000
                          passed: 4962
                          replaced: 38
                        download_url: /v1/orders/ord_01JB4N9S3W2X6Y7Z8A1B2C3D4E/download
                        receipt_url: /v1/orders/ord_01JB4N9S3W2X6Y7Z8A1B2C3D4E/receipt
                        validation_report:
                          pdf_url: https://mydatahub.emailmovers.com/orders/ord_01JB4N9S3W2X6Y7Z8A1B2C3D4E/validation-report.pdf
                          json_url: https://mydatahub.emailmovers.com/orders/ord_01JB4N9S3W2X6Y7Z8A1B2C3D4E/validation-report.json
                        buyer_kit:
                          draft_lia_url: https://mydatahub.emailmovers.com/orders/ord_01JB4N9S3W2X6Y7Z8A1B2C3D4E/kit/lia
                          first_email_wording_url: https://mydatahub.emailmovers.com/orders/ord_01JB4N9S3W2X6Y7Z8A1B2C3D4E/kit/first-email
                          article30_stub_url: https://mydatahub.emailmovers.com/orders/ord_01JB4N9S3W2X6Y7Z8A1B2C3D4E/kit/article30
                          due_diligence_file_url: https://mydatahub.emailmovers.com/orders/ord_01JB4N9S3W2X6Y7Z8A1B2C3D4E/kit/due-diligence
                        created_at: "2026-09-26T10:16:00Z"
                        ready_at: "2026-09-26T10:16:04Z"
                        failure: null
                    has_more: false
                    next_cursor: null
        "400":
          $ref: "#/components/responses/BadRequest"
        "401":
          $ref: "#/components/responses/Unauthorized"
        "403":
          $ref: "#/components/responses/Forbidden"
        "422":
          $ref: "#/components/responses/ValidationFailed"
        "429":
          $ref: "#/components/responses/RateLimited"
        default:
          $ref: "#/components/responses/ServerError"
      x-cost-credits: 0
      x-free: true
      x-anonymous: false
      x-mcp-tool: get_order
  /orders/{id}:
    get:
      operationId: getOrder
      summary: Get an order
      description: Status (queued, building, verifying, ready, failed, cancelled), counts, charges, the licence, the buyer kit and the links to records, download and receipt.
      tags:
        - Orders
      security:
        - OAuth2:
            - orders:read
        - ApiKey:
            - orders:read
      parameters:
        - $ref: "#/components/parameters/MyDataHubVersion"
        - name: id
          in: path
          required: true
          description: "Format: an order id: ord_ followed by a 26 character ULID in upper case Crockford base32, such as ord_01JB4N9S3W2X6Y7Z8A1B2C3D4E."
          schema:
            type: string
            pattern: ^ord_[0-7][0-9A-HJKMNP-TV-Z]{25}$
            examples:
              - ord_01JB4N9S3W2X6Y7Z8A1B2C3D4E
          example: ord_01JB4N9S3W2X6Y7Z8A1B2C3D4E
      responses:
        "200":
          description: The order.
          headers:
            RateLimit:
              $ref: "#/components/headers/RateLimit"
            RateLimit-Policy:
              $ref: "#/components/headers/RateLimit-Policy"
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Order"
              examples:
                illustrative:
                  summary: A ready order
                  value:
                    id: ord_01JB4N9S3W2X6Y7Z8A1B2C3D4E
                    status: ready
                    credits_charged: 5000
                    records: 5000
                    confirmed_by:
                      method: agent_key
                      key_id: key_01JB3Z4Q2V7XN3M9R6T1Y5W8ZA
                      client_id: https://claude.ai/oauth/claude-code-client-metadata
                      user_agent: claude-code/2.1
                      mtls_certificate_fingerprint: null
                      web_bot_auth_signature_agent: null
                      confirmed_at: "2026-09-26T10:16:00Z"
                    records_url: /v1/orders/ord_01JB4N9S3W2X6Y7Z8A1B2C3D4E/records
                    quote_id: qte_01JB4M2R6T1Y5W8ZAK8Q2V7XN3
                    count_id: cnt_7K2PQ4MAXN3R6TBY5WDZ
                    credits_remaining: 1200
                    cost_gbp: 1495
                    licence_expires_at: "2027-09-26"
                    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
                    name: Leeds marketing Q4
                    dataset_version: 2026-W39
                    field_set: standard
                    delivery:
                      - api
                      - csv
                    verification:
                      checked: 5000
                      passed: 4962
                      replaced: 38
                    download_url: /v1/orders/ord_01JB4N9S3W2X6Y7Z8A1B2C3D4E/download
                    receipt_url: /v1/orders/ord_01JB4N9S3W2X6Y7Z8A1B2C3D4E/receipt
                    validation_report:
                      pdf_url: https://mydatahub.emailmovers.com/orders/ord_01JB4N9S3W2X6Y7Z8A1B2C3D4E/validation-report.pdf
                      json_url: https://mydatahub.emailmovers.com/orders/ord_01JB4N9S3W2X6Y7Z8A1B2C3D4E/validation-report.json
                    buyer_kit:
                      draft_lia_url: https://mydatahub.emailmovers.com/orders/ord_01JB4N9S3W2X6Y7Z8A1B2C3D4E/kit/lia
                      first_email_wording_url: https://mydatahub.emailmovers.com/orders/ord_01JB4N9S3W2X6Y7Z8A1B2C3D4E/kit/first-email
                      article30_stub_url: https://mydatahub.emailmovers.com/orders/ord_01JB4N9S3W2X6Y7Z8A1B2C3D4E/kit/article30
                      due_diligence_file_url: https://mydatahub.emailmovers.com/orders/ord_01JB4N9S3W2X6Y7Z8A1B2C3D4E/kit/due-diligence
                    created_at: "2026-09-26T10:16:00Z"
                    ready_at: "2026-09-26T10:16:04Z"
                    failure: null
        "400":
          $ref: "#/components/responses/BadRequest"
        "401":
          $ref: "#/components/responses/Unauthorized"
        "403":
          $ref: "#/components/responses/Forbidden"
        "404":
          $ref: "#/components/responses/NotFound"
        "429":
          $ref: "#/components/responses/RateLimited"
        default:
          $ref: "#/components/responses/ServerError"
      x-cost-credits: 0
      x-free: true
      x-anonymous: false
      x-mcp-tool: get_order
  /orders/{id}/cancel:
    post:
      operationId: cancelOrder
      summary: Cancel a queued order
      description: Cancel an order while it is queued; its credits are refunded in full. Later statuses return 409.
      tags:
        - Orders
      security:
        - OAuth2:
            - orders:write
        - ApiKey:
            - orders:write
      parameters:
        - $ref: "#/components/parameters/MyDataHubVersion"
        - $ref: "#/components/parameters/IdempotencyKey"
        - name: id
          in: path
          required: true
          description: "Format: an order id: ord_ followed by a 26 character ULID in upper case Crockford base32, such as ord_01JB4N9S3W2X6Y7Z8A1B2C3D4E."
          schema:
            type: string
            pattern: ^ord_[0-7][0-9A-HJKMNP-TV-Z]{25}$
            examples:
              - ord_01JB4N9S3W2X6Y7Z8A1B2C3D4E
          example: ord_01JB4N9S3W2X6Y7Z8A1B2C3D4E
      responses:
        "200":
          description: The cancelled order.
          headers:
            RateLimit:
              $ref: "#/components/headers/RateLimit"
            RateLimit-Policy:
              $ref: "#/components/headers/RateLimit-Policy"
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Order"
              examples:
                illustrative:
                  summary: Cancel a queued order
                  value:
                    id: ord_01JB4N9S3W2X6Y7Z8A1B2C3D4E
                    status: cancelled
                    credits_charged: 0
                    records: 5000
                    confirmed_by:
                      method: agent_key
                      key_id: key_01JB3Z4Q2V7XN3M9R6T1Y5W8ZA
                      client_id: https://claude.ai/oauth/claude-code-client-metadata
                    records_url: /v1/orders/ord_01JB4N9S3W2X6Y7Z8A1B2C3D4E/records
                    quote_id: qte_01JB4M2R6T1Y5W8ZAK8Q2V7XN3
                    count_id: cnt_7K2PQ4MAXN3R6TBY5WDZ
                    credits_remaining: 6200
                    cost_gbp: 0
                    licence_expires_at: "2027-09-26"
                    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
        "400":
          $ref: "#/components/responses/BadRequest"
        "401":
          $ref: "#/components/responses/Unauthorized"
        "403":
          $ref: "#/components/responses/Forbidden"
        "404":
          $ref: "#/components/responses/NotFound"
        "409":
          $ref: "#/components/responses/Conflict"
        "429":
          $ref: "#/components/responses/RateLimited"
        default:
          $ref: "#/components/responses/ServerError"
      x-cost-credits: 0
      x-free: true
      x-anonymous: false
      x-idempotency-key: optional
  /orders/{id}/receipt:
    get:
      operationId: getComplianceReceipt
      summary: Get the compliance receipt
      description: "The order's compliance receipt as an ES256 JWS with its decoded claims and a PDF twin: records delivered, suppression removals, counts by subscriber class, TPS status and Article 14 status, the licence and the file hash."
      tags:
        - Orders
      security:
        - OAuth2:
            - orders:read
        - ApiKey:
            - orders:read
      parameters:
        - $ref: "#/components/parameters/MyDataHubVersion"
        - name: id
          in: path
          required: true
          description: "Format: an order id: ord_ followed by a 26 character ULID in upper case Crockford base32, such as ord_01JB4N9S3W2X6Y7Z8A1B2C3D4E."
          schema:
            type: string
            pattern: ^ord_[0-7][0-9A-HJKMNP-TV-Z]{25}$
            examples:
              - ord_01JB4N9S3W2X6Y7Z8A1B2C3D4E
          example: ord_01JB4N9S3W2X6Y7Z8A1B2C3D4E
      responses:
        "200":
          description: The receipt.
          headers:
            RateLimit:
              $ref: "#/components/headers/RateLimit"
            RateLimit-Policy:
              $ref: "#/components/headers/RateLimit-Policy"
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ComplianceReceipt"
              examples:
                illustrative:
                  summary: Receipt for the Leeds order
                  value:
                    id: rcpt_01JB4NA2Q5W8ZAK8Q2V7XN3M9R
                    order_id: ord_01JB4N9S3W2X6Y7Z8A1B2C3D4E
                    jws: eyJhbGciOiJFUzI1NiIsImtpZCI6InJjcHQtMjAyNi0wOSJ9.eyJyZWNlaXB0X2lkIjoicmNwdF8wMUpCNE5BMlE1VzhaQUs4UTJWN1hOM005UiJ9.MEUCIQDexampleSignatureOnly
                    alg: ES256
                    kid: rcpt-2026-09
                    jwks_url: https://mydatahub.emailmovers.com/.well-known/jwks.json
                    pdf_url: https://mydatahub.emailmovers.com/receipts/rcpt_01JB4NA2Q5W8ZAK8Q2V7XN3M9R
                    claims:
                      receipt_id: rcpt_01JB4NA2Q5W8ZAK8Q2V7XN3M9R
                      order_id: ord_01JB4N9S3W2X6Y7Z8A1B2C3D4E
                      issued_at: "2026-09-26T10:16:04Z"
                      dataset_version: 2026-W39
                      records_delivered: 5000
                      removed_by_suppression: 41
                      counts:
                        subscriber_class:
                          ltd: 4610
                          plc: 120
                          llp: 180
                          scottish_partnership: 0
                          public_body: 60
                          charity_incorporated: 30
                          sole_trader: 0
                          ordinary_partnership: 0
                          unknown: 0
                        tps_ctps_status:
                          clear: 4380
                          tps_registered: 0
                          ctps_registered: 0
                          tps_or_ctps_registered: 0
                          not_screened: 620
                        article14_status:
                          notified: 4990
                          publicly_notified_disproportionate_effort: 0
                          pending_within_window: 10
                      lawful_basis: legitimate_interests_art6_1_f
                      lia_ref: EM-LIA-2026-03
                      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
                      residency_statement_version: 2026-10
                      sub_processor_list_version: 2026-10
                      verified_within_days:
                        within_30_days: 61.2
                        within_60_days: 79.5
                        within_90_days: 88.4
                      content_sha256: 3a7bd3e2360a3d29eea436fcfb7e44c735d117c42d1c1835420b6b9942dd4f1b
        "400":
          $ref: "#/components/responses/BadRequest"
        "401":
          $ref: "#/components/responses/Unauthorized"
        "403":
          $ref: "#/components/responses/Forbidden"
        "404":
          $ref: "#/components/responses/NotFound"
        "409":
          $ref: "#/components/responses/Conflict"
        "429":
          $ref: "#/components/responses/RateLimited"
        default:
          $ref: "#/components/responses/ServerError"
      x-cost-credits: 0
      x-free: true
      x-anonymous: false
      x-mcp-tool: get_compliance_receipt
  /orders/{id}/records:
    get:
      operationId: listOrderRecords
      summary: Fetch order records
      description: Delivered records in token-budgeted pages (default 50, up to 500), as JSON with the licence block on every page or as CSV. Fetching again under licence costs 0 credits.
      tags:
        - Records
      security:
        - OAuth2:
            - records:read
        - ApiKey:
            - records:read
      parameters:
        - $ref: "#/components/parameters/MyDataHubVersion"
        - name: id
          in: path
          required: true
          description: "Format: an order id: ord_ followed by a 26 character ULID in upper case Crockford base32, such as ord_01JB4N9S3W2X6Y7Z8A1B2C3D4E."
          schema:
            type: string
            pattern: ^ord_[0-7][0-9A-HJKMNP-TV-Z]{25}$
            examples:
              - ord_01JB4N9S3W2X6Y7Z8A1B2C3D4E
          example: ord_01JB4N9S3W2X6Y7Z8A1B2C3D4E
        - name: cursor
          in: query
          required: false
          description: An opaque cursor from next_cursor. Pass it back unchanged.
          schema:
            type: string
            minLength: 1
            maxLength: 512
        - name: limit
          in: query
          required: false
          description: Records per page, 1 to 500. Default 50.
          schema:
            default: 50
            type: integer
            minimum: 1
            maximum: 500
          example: 50
        - name: format
          in: query
          required: false
          description: json (default) or csv.
          schema:
            default: json
            type: string
            enum:
              - json
              - csv
        - name: fields
          in: query
          required: false
          description: Columns to return, comma separated; default the order's field set.
          schema:
            type: array
            items:
              type: string
              pattern: ^[a-z0-9_]+$
          style: form
          explode: false
      responses:
        "200":
          description: A page of records.
          headers:
            RateLimit:
              $ref: "#/components/headers/RateLimit"
            RateLimit-Policy:
              $ref: "#/components/headers/RateLimit-Policy"
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/RecordsPage"
              examples:
                illustrative:
                  summary: The first page of the Leeds order
                  value:
                    order_id: ord_01JB4N9S3W2X6Y7Z8A1B2C3D4E
                    format: json
                    fields:
                      - first_name
                      - last_name
                      - title
                      - email
                      - phone
                      - company_name
                      - postcode
                    data:
                      - id: contact_01JA9X2V7XN3M9R6T1Y5W8ZAK8
                        dataset_version: 2026-W39
                        contact:
                          first_name: Jo
                          last_name: Example
                          title: Marketing Director
                          title_family: Marketing Director
                          job_function: Marketing
                          job_level_rank: 6
                          job_level: Director
                        email:
                          address: jo.example@example.co.uk
                          type: named
                          deliverability_score: 88
                          verified_at: "2026-09-10"
                          result: ok
                        phone:
                          "e164": "+441130000001"
                          line_type: landline
                          tps_ctps_status: clear
                          screened_at: "2026-09-12"
                        company:
                          name: Example Manufacturing Ltd
                          companies_house_number: "12345678"
                          status: active
                          type: ltd
                          subscriber_class: ltd
                          sic_2007:
                            - code: "25620"
                              label: Machining
                          employee_band: 100-199
                          turnover_band: 11M-20M
                          website_domain: example.co.uk
                        site:
                          address_lines:
                            - 1 Example Street
                          town: Leeds
                          county: West Yorkshire
                          postcode: LS11 5AA
                          region: yorkshire_and_the_humber
                        compliance:
                          lawful_basis: legitimate_interests_art6_1_f
                          lia_ref: EM-LIA-2026-03
                          subscriber_class: ltd
                          corporate_subscriber: true
                          email_marketing_basis: pecr_corporate_subscriber
                          subscriber_evidence:
                            companies_house_number: "12345678"
                            company_type: ltd
                          article14_status: notified
                          article14_notified_at: "2026-06-14"
                          notice_version: 2026-02
                          provenance:
                            company:
                              source: companies_house
                              as_of: "2026-09-21"
                            contact:
                              source: emailmovers_research
                              compiled_at: "2026-05-02"
                              last_verified_at: "2026-08-30"
                              method: telephone
                            email:
                              verified_at: "2026-09-10"
                              result: ok
                              provider_location: EU
                          tps_ctps_status: clear
                          tps_ctps_screened_at: "2026-09-12"
                          tps_ctps_valid_until: "2026-10-10"
                          suppression_status: none
                          record_age_days: 147
                          attribution: Contains public sector information licensed under the Open Government Licence v3.0
                        licence_expires_at: "2027-09-26"
                    has_more: true
                    next_cursor: b3JkXzAxSkI0TjlTM1cyWDZZN1o4QTFCMkMzRDRFOjUw
                    remaining: 4950
                    approx_tokens: 7400
                    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
            text/csv:
              schema:
                type: string
                description: CSV with a header row; compliance fields as columns.
              example: |
                mydatahub_record_id,first_name,last_name,title,email,company_name,postcode,lawful_basis,article14_status
                contact_01JA9X2V7XN3M9R6T1Y5W8ZAK8,Jo,Example,Marketing Director,jo.example@example.co.uk,Example Manufacturing Ltd,LS11 5AA,legitimate_interests_art6_1_f,notified
        "400":
          $ref: "#/components/responses/BadRequest"
        "401":
          $ref: "#/components/responses/Unauthorized"
        "403":
          $ref: "#/components/responses/Forbidden"
        "404":
          $ref: "#/components/responses/NotFound"
        "409":
          $ref: "#/components/responses/Conflict"
        "422":
          $ref: "#/components/responses/ValidationFailed"
        "429":
          $ref: "#/components/responses/RateLimited"
        default:
          $ref: "#/components/responses/ServerError"
      x-cost-credits: 0
      x-free: true
      x-anonymous: false
      x-mcp-tool: fetch_order_records
  /orders/{id}/download:
    get:
      operationId: downloadOrder
      summary: Download order records
      description: Redirects to a single-use signed URL for the CSV that lives 5 minutes, bound to the account and logged. Re-downloads under licence cost 0 credits.
      tags:
        - Records
      security:
        - OAuth2:
            - records:read
        - ApiKey:
            - records:read
      parameters:
        - $ref: "#/components/parameters/MyDataHubVersion"
        - name: id
          in: path
          required: true
          description: "Format: an order id: ord_ followed by a 26 character ULID in upper case Crockford base32, such as ord_01JB4N9S3W2X6Y7Z8A1B2C3D4E."
          schema:
            type: string
            pattern: ^ord_[0-7][0-9A-HJKMNP-TV-Z]{25}$
            examples:
              - ord_01JB4N9S3W2X6Y7Z8A1B2C3D4E
          example: ord_01JB4N9S3W2X6Y7Z8A1B2C3D4E
      responses:
        "302":
          description: "Found: follow Location to the signed file."
          headers:
            RateLimit:
              $ref: "#/components/headers/RateLimit"
            RateLimit-Policy:
              $ref: "#/components/headers/RateLimit-Policy"
            Location:
              $ref: "#/components/headers/Location"
        "400":
          $ref: "#/components/responses/BadRequest"
        "401":
          $ref: "#/components/responses/Unauthorized"
        "403":
          $ref: "#/components/responses/Forbidden"
        "404":
          $ref: "#/components/responses/NotFound"
        "409":
          $ref: "#/components/responses/Conflict"
        "429":
          $ref: "#/components/responses/RateLimited"
        default:
          $ref: "#/components/responses/ServerError"
      x-cost-credits: 0
      x-free: true
      x-anonymous: false
  /companies/lookup:
    post:
      operationId: lookupCompany
      summary: Look up a company
      description: Companies House fields for one company, by number, domain or name, and for signed-in callers the contacts we hold there by job level (a single company count needs an account and is logged; signed out, the counts are null). No contact details, and no charge (to confirm).
      tags:
        - Lookups
      security:
        - {}
        - OAuth2:
            - counts:read
        - ApiKey:
            - counts:read
      parameters:
        - $ref: "#/components/parameters/MyDataHubVersion"
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/CompanyLookupRequest"
            examples:
              illustrative:
                summary: By Companies House number
                value:
                  company_number: "12345678"
      responses:
        "200":
          description: The company.
          headers:
            RateLimit:
              $ref: "#/components/headers/RateLimit"
            RateLimit-Policy:
              $ref: "#/components/headers/RateLimit-Policy"
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/CompanyLookup"
              examples:
                illustrative:
                  summary: By Companies House number
                  value:
                    match_confidence: 1
                    dataset_version: 2026-W39
                    company:
                      companies_house_number: "12345678"
                      name: Example Manufacturing Ltd
                      status: active
                      type: ltd
                      subscriber_class: ltd
                      incorporation_date: "2004-02-19"
                      sic_2007:
                        - code: "25620"
                          label: Machining
                      employee_band: 100-199
                      turnover_band: 11M-20M
                      website_domain: example.co.uk
                      registered_office:
                        town: Leeds
                        postcode: LS11 5AA
                    contacts_held: 14
                    contacts_by_level:
                      - level: Director
                        contacts: 3
                      - level: Manager
                        contacts: 6
                    compliance:
                      lawful_basis: legitimate_interests_art6_1_f
                      subscriber_class: ltd
                      corporate_subscriber: true
                      email_marketing_basis: pecr_corporate_subscriber
                      subscriber_evidence:
                        companies_house_number: "12345678"
                        company_type: ltd
                      provenance:
                        company:
                          source: companies_house
                          as_of: "2026-09-21"
                      attribution: Contains public sector information licensed under the Open Government Licence v3.0
                    disclosure:
                      level: exact
                      rounded_to: null
                      secondary_suppression: false
                      notes: []
                    credits_charged: 0
        "400":
          $ref: "#/components/responses/BadRequest"
        "401":
          $ref: "#/components/responses/Unauthorized"
        "422":
          $ref: "#/components/responses/ValidationFailed"
        "429":
          $ref: "#/components/responses/RateLimited"
        default:
          $ref: "#/components/responses/ServerError"
      x-cost-credits: 0
      x-free: true
      x-anonymous: true
      x-mcp-tool: lookup_company
  /contacts/enrich:
    post:
      operationId: enrichContact
      summary: Enrich a contact (spends credits)
      description: The full record for one known person, by email or by name and company. Charges 1 credit only on a confident match, and 0 when you already hold the record under licence or nothing matches. It spends, so it needs a human confirmation (MCP elicitation or a confirmation_token) or an agent key whose allowed_tools include enrich_contact, within its per-order cap and remaining monthly budget; otherwise 403 confirmation-required or agent-budget-exceeded, and 402 when the balance is short. An account made by self-serve sign-up confirms its business name and that it is a UK business first (PATCH /v1/account), or the answer is 403 business-details-required. The record comes under the Data Licence and Sharing Agreement, so the version in force is accepted as for an order, or the answer is 403 licence-acceptance-required with the approval_url. Nothing is charged on a refusal. Lawful basis for named work emails is UK GDPR Article 6(1)(f) legitimate interests; sole traders and ordinary partnerships are individual subscribers and are left out of email audiences by default.
      tags:
        - Lookups
      security:
        - OAuth2:
            - enrich:write
        - ApiKey:
            - enrich:write
      parameters:
        - $ref: "#/components/parameters/MyDataHubVersion"
        - $ref: "#/components/parameters/IdempotencyKeyRequired"
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/EnrichRequest"
            examples:
              illustrative:
                summary: Enrich by name and company number
                value:
                  first_name: Jo
                  last_name: Example
                  company_number: "12345678"
      responses:
        "200":
          description: The match, or no match.
          headers:
            RateLimit:
              $ref: "#/components/headers/RateLimit"
            RateLimit-Policy:
              $ref: "#/components/headers/RateLimit-Policy"
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Enrichment"
              examples:
                illustrative:
                  summary: Enrich by name and company number
                  value:
                    matched: true
                    match_confidence: 0.97
                    already_licensed: false
                    record:
                      id: contact_01JA9X2V7XN3M9R6T1Y5W8ZAK8
                      dataset_version: 2026-W39
                      contact:
                        first_name: Jo
                        last_name: Example
                        title: Marketing Director
                        title_family: Marketing Director
                        job_function: Marketing
                        job_level_rank: 6
                        job_level: Director
                      email:
                        address: jo.example@example.co.uk
                        type: named
                        deliverability_score: 88
                        verified_at: "2026-09-10"
                        result: ok
                      phone:
                        "e164": "+441130000001"
                        line_type: landline
                        tps_ctps_status: clear
                        screened_at: "2026-09-12"
                      company:
                        name: Example Manufacturing Ltd
                        companies_house_number: "12345678"
                        status: active
                        type: ltd
                        subscriber_class: ltd
                        sic_2007:
                          - code: "25620"
                            label: Machining
                        employee_band: 100-199
                        turnover_band: 11M-20M
                        website_domain: example.co.uk
                      site:
                        address_lines:
                          - 1 Example Street
                        town: Leeds
                        county: West Yorkshire
                        postcode: LS11 5AA
                        region: yorkshire_and_the_humber
                      compliance:
                        lawful_basis: legitimate_interests_art6_1_f
                        lia_ref: EM-LIA-2026-03
                        subscriber_class: ltd
                        corporate_subscriber: true
                        email_marketing_basis: pecr_corporate_subscriber
                        subscriber_evidence:
                          companies_house_number: "12345678"
                          company_type: ltd
                        article14_status: notified
                        article14_notified_at: "2026-06-14"
                        notice_version: 2026-02
                        provenance:
                          company:
                            source: companies_house
                            as_of: "2026-09-21"
                          contact:
                            source: emailmovers_research
                            compiled_at: "2026-05-02"
                            last_verified_at: "2026-08-30"
                            method: telephone
                          email:
                            verified_at: "2026-09-10"
                            result: ok
                            provider_location: EU
                        tps_ctps_status: clear
                        tps_ctps_screened_at: "2026-09-12"
                        tps_ctps_valid_until: "2026-10-10"
                        suppression_status: none
                        record_age_days: 147
                        attribution: Contains public sector information licensed under the Open Government Licence v3.0
                      licence_expires_at: "2027-09-26"
                    credits_charged: 1
                    credits_remaining: 1199
                    cost_gbp: 0.3
                    licence_expires_at: "2027-09-26"
        "400":
          $ref: "#/components/responses/BadRequest"
        "401":
          $ref: "#/components/responses/Unauthorized"
        "402":
          $ref: "#/components/responses/InsufficientCredits"
        "403":
          $ref: "#/components/responses/Forbidden"
        "409":
          $ref: "#/components/responses/Conflict"
        "422":
          $ref: "#/components/responses/ValidationFailed"
        "429":
          $ref: "#/components/responses/RateLimited"
        default:
          $ref: "#/components/responses/ServerError"
      x-cost-credits: 1
      x-cost-unit: match
      x-cost-note: 1 credit on a confident match; 0 on no match or when already licensed.
      x-free: false
      x-anonymous: false
      x-idempotency-key: required
      x-mcp-tool: enrich_contact
  /suppressions:
    get:
      operationId: listSuppressions
      summary: List suppression lists
      description: The organisation's suppression lists. With since, only lists changed after that time, each with the records added since, so objections that reach records you hold can be pulled as well as received by webhook.
      tags:
        - Suppressions
      security:
        - OAuth2:
            - suppressions:read
        - ApiKey:
            - suppressions:read
      parameters:
        - $ref: "#/components/parameters/MyDataHubVersion"
        - name: since
          in: query
          required: false
          description: Only lists changed after this time.
          schema:
            type: string
            format: date-time
        - name: limit
          in: query
          required: false
          description: Items per page, 1 to 100. Default 25.
          schema:
            default: 25
            type: integer
            minimum: 1
            maximum: 100
        - name: cursor
          in: query
          required: false
          description: An opaque cursor from next_cursor. Pass it back unchanged.
          schema:
            type: string
            minLength: 1
            maxLength: 512
      responses:
        "200":
          description: A page of suppression lists.
          headers:
            RateLimit:
              $ref: "#/components/headers/RateLimit"
            RateLimit-Policy:
              $ref: "#/components/headers/RateLimit-Policy"
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/SuppressionListPage"
              examples:
                illustrative:
                  summary: One uploaded list
                  value:
                    data:
                      - id: sup_01JB4H7Q2V7XN3M9R6T1Y5W8ZA
                        name: Existing customers
                        kind: email
                        source: upload
                        row_count: 1198
                        created_at: "2026-09-20T14:02:00Z"
                        updated_at: "2026-09-20T14:02:00Z"
                        created_by: usr_01JA0Q3V7XN3M9R6T1Y5W8ZAK8
                    has_more: false
                    next_cursor: null
        "400":
          $ref: "#/components/responses/BadRequest"
        "401":
          $ref: "#/components/responses/Unauthorized"
        "403":
          $ref: "#/components/responses/Forbidden"
        "422":
          $ref: "#/components/responses/ValidationFailed"
        "429":
          $ref: "#/components/responses/RateLimited"
        default:
          $ref: "#/components/responses/ServerError"
      x-cost-credits: 0
      x-free: true
      x-anonymous: false
    post:
      operationId: createSuppressionList
      summary: Upload a suppression list
      description: Upload emails, domains, phones, company numbers or title terms, raw or already hashed. Values are normalised and stored as SHA-256 hashes only. The list applies to every later count and order at no charge, and each count reports what it removed. The organisation accepts the Article 28 data processing agreement in force first (POST /v1/account/agreements, or dpa_accepted_version on this upload from a signed-in person); until then the answer is 403 dpa-acceptance-required and nothing is uploaded.
      tags:
        - Suppressions
      security:
        - OAuth2:
            - suppressions:write
        - ApiKey:
            - suppressions:write
      parameters:
        - $ref: "#/components/parameters/MyDataHubVersion"
        - $ref: "#/components/parameters/IdempotencyKey"
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/SuppressionListRequest"
            examples:
              illustrative:
                summary: Upload existing customers
                value:
                  name: Existing customers
                  kind: email
                  values:
                    - jo.example@example.co.uk
                    - sam@example.org
      responses:
        "201":
          description: The saved list and what was read.
          headers:
            RateLimit:
              $ref: "#/components/headers/RateLimit"
            RateLimit-Policy:
              $ref: "#/components/headers/RateLimit-Policy"
            Location:
              $ref: "#/components/headers/Location"
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/SuppressionUpload"
              examples:
                illustrative:
                  summary: Upload existing customers
                  value:
                    list:
                      id: sup_01JB4H7Q2V7XN3M9R6T1Y5W8ZA
                      name: Existing customers
                      kind: email
                      source: upload
                      row_count: 1198
                      created_at: "2026-09-20T14:02:00Z"
                      updated_at: "2026-09-20T14:02:00Z"
                      created_by: usr_01JA0Q3V7XN3M9R6T1Y5W8ZAK8
                    rows_read: 1204
                    rows_valid: 1198
                    rows_skipped:
                      - row: 17
                        reason: Not an email address.
        "400":
          $ref: "#/components/responses/BadRequest"
        "401":
          $ref: "#/components/responses/Unauthorized"
        "403":
          $ref: "#/components/responses/Forbidden"
        "409":
          $ref: "#/components/responses/Conflict"
        "422":
          $ref: "#/components/responses/ValidationFailed"
        "429":
          $ref: "#/components/responses/RateLimited"
        default:
          $ref: "#/components/responses/ServerError"
      x-cost-credits: 0
      x-free: true
      x-anonymous: false
      x-idempotency-key: optional
  /suppressions/check:
    post:
      operationId: checkSuppression
      summary: Check suppression
      description: Suppression status per record id, as of now. Run it before any send.
      tags:
        - Suppressions
      security:
        - OAuth2:
            - suppressions:read
        - ApiKey:
            - suppressions:read
      parameters:
        - $ref: "#/components/parameters/MyDataHubVersion"
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/SuppressionCheckRequest"
            examples:
              illustrative:
                summary: Check one record
                value:
                  record_ids:
                    - contact_01JA9X2V7XN3M9R6T1Y5W8ZAK8
      responses:
        "200":
          description: Status per record.
          headers:
            RateLimit:
              $ref: "#/components/headers/RateLimit"
            RateLimit-Policy:
              $ref: "#/components/headers/RateLimit-Policy"
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/SuppressionCheck"
              examples:
                illustrative:
                  summary: Check one record
                  value:
                    checked_at: "2026-10-01T09:00:00Z"
                    results:
                      - record_id: contact_01JA9X2V7XN3M9R6T1Y5W8ZAK8
                        suppressed: false
                        suppression_status: none
                        list_ids: []
        "400":
          $ref: "#/components/responses/BadRequest"
        "401":
          $ref: "#/components/responses/Unauthorized"
        "403":
          $ref: "#/components/responses/Forbidden"
        "422":
          $ref: "#/components/responses/ValidationFailed"
        "429":
          $ref: "#/components/responses/RateLimited"
        default:
          $ref: "#/components/responses/ServerError"
      x-cost-credits: 0
      x-free: true
      x-anonymous: false
      x-mcp-tool: check_suppression
  /objections:
    post:
      operationId: reportObjection
      summary: Report an objection
      description: Record an objection to direct marketing. Scope licensee suppresses the record for your organisation at once; scope emailmovers suppresses it database-wide within one working day and tells every licensee holding it.
      tags:
        - Suppressions
      security:
        - OAuth2:
            - suppressions:write
        - ApiKey:
            - suppressions:write
      parameters:
        - $ref: "#/components/parameters/MyDataHubVersion"
        - $ref: "#/components/parameters/IdempotencyKey"
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/ObjectionRequest"
            examples:
              illustrative:
                summary: An objection received by email
                value:
                  record_id: contact_01JA9X2V7XN3M9R6T1Y5W8ZAK8
                  channel: email
                  received_at: "2026-10-01T08:30:00Z"
                  scope: emailmovers
                  note: Replied asking not to be contacted again.
      responses:
        "201":
          description: The suppression it created.
          headers:
            RateLimit:
              $ref: "#/components/headers/RateLimit"
            RateLimit-Policy:
              $ref: "#/components/headers/RateLimit-Policy"
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Objection"
              examples:
                illustrative:
                  summary: An objection received by email
                  value:
                    suppression_id: sup_01JA0Q5V7XN3M9R6T1Y5W8ZAK8
                    record_id: contact_01JA9X2V7XN3M9R6T1Y5W8ZAK8
                    scope: emailmovers
                    status: pending
                    suppressed_at: null
                    effective_by: "2026-10-02T17:00:00Z"
        "400":
          $ref: "#/components/responses/BadRequest"
        "401":
          $ref: "#/components/responses/Unauthorized"
        "403":
          $ref: "#/components/responses/Forbidden"
        "409":
          $ref: "#/components/responses/Conflict"
        "422":
          $ref: "#/components/responses/ValidationFailed"
        "429":
          $ref: "#/components/responses/RateLimited"
        default:
          $ref: "#/components/responses/ServerError"
      x-cost-credits: 0
      x-free: true
      x-anonymous: false
      x-idempotency-key: optional
      x-mcp-tool: report_objection
  /trust:
    get:
      operationId: getTrust
      summary: Get the trust document
      description: "The trust page as data: ICO registration, lawful basis and LIA, privacy notice, residency statement, sub-processors, retention, complaints and security. Mirrored at https://mydatahub.emailmovers.com/trust.json."
      tags:
        - Compliance
      security: []
      parameters:
        - $ref: "#/components/parameters/MyDataHubVersion"
      responses:
        "200":
          description: The trust document.
          headers:
            RateLimit:
              $ref: "#/components/headers/RateLimit"
            RateLimit-Policy:
              $ref: "#/components/headers/RateLimit-Policy"
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/TrustDocument"
              examples:
                illustrative:
                  summary: The trust document
                  value:
                    updated_at: "2026-09-26T09:00:00Z"
                    company:
                      legal_name: Emailmovers Limited
                      company_number: "05046417"
                      trading_as: Emailmovers Ltd
                    ico_registration:
                      number: Z9714386
                      expires_on: "2026-12-18"
                      register_url: https://ico.org.uk/ESDWebPages/Entry/Z9714386
                    lawful_basis:
                      basis: legitimate_interests_art6_1_f
                      lia_ref: EM-LIA-2026-03
                      lia_url: https://emailmovers.com/trust/lia
                    privacy_notice:
                      version: 2026-02
                      url: https://emailmovers.com/trust/privacy-notice
                      versions:
                        - version: 2026-02
                          published_at: "2026-02-05"
                          url: https://emailmovers.com/trust/privacy-notice/2026-02
                    residency:
                      statement: Data is held in the UK (Google Cloud London). Email addresses are verified in the EU by Millionverifier, with some verification traffic passing through US providers under standard contractual clauses. Telephone numbers are checked by Telnyx in the United States under the UK Extension to the Data Privacy Framework. Every telephone number is screened against the TPS and CTPS each night through a licensed screening service based in the UK, which processes the numbers in the UK. Payments are processed by Stripe.
                      version: 2026-10
                    sub_processors:
                      version: 2026-10
                      notice_days: 30
                      notice_channels:
                        - email
                        - webhook
                      list:
                        - name: Millionverifier
                          purpose: Email verification
                          location: EU (Hungary), some US network traffic
                          transfer_mechanism: Standard contractual clauses
                          added_at: "2026-09-01"
                        - name: Telnyx
                          purpose: Telephone line type checks
                          location: United States
                          transfer_mechanism: UK Extension to the Data Privacy Framework
                          added_at: "2026-09-01"
                        - name: A licensed TPS and CTPS screening service
                          purpose: TPS and CTPS screening of every telephone number each night
                          location: United Kingdom
                          transfer_mechanism: No transfer outside the UK
                          added_at: "2026-10-18"
                      changes:
                        - date: "2026-09-01"
                          name: Telnyx
                          change: added
                          summary: Telephone line type and carrier checks at ingest and every 180 days.
                        - date: "2026-10-18"
                          name: A licensed TPS and CTPS screening service
                          change: added
                          summary: Every telephone number screened against the TPS and CTPS each night, in the UK.
                    retention:
                      - category: Audit logs
                        period: Licence term plus 12 months
                        status: to_confirm
                    complaints:
                      url: https://emailmovers.com/trust/complaints
                      email: null
                      acknowledgement_days: 30
                    data_protection_lead: null
                    datasets:
                      - id: uk_contacts
                        name: UK contacts
                        status: live
                        owner: Emailmovers Ltd
                        source: Emailmovers research; Companies House (OGL v3.0); ONSPD (OGL v3.0)
                        licence: 12 month multi-use licence; resale prohibited
                        refresh: Immutable weekly build every Monday; same-day suppression overlay
                        residency: Held in the UK (Google Cloud London)
                        regulator: ICO
                        warranty: Replacement when hard bounces exceed 10 percent of an order
                        credits_per_record: 1
                        credits_status: confirmed
                        current_version: 2026-W39
                        versions_available: 52
                    security:
                      certifications:
                        - name: Cyber Essentials
                          status: target
                          date: 2026-10
                        - name: Cyber Essentials Plus
                          status: target
                          date: 2027-01
                      encryption:
                        at_rest: AES-256
                        in_transit_minimum: TLS 1.2
                        key_rotation: KMS managed key rotation
                      data_flow_diagram_url: null
                      pen_test_summary_url: null
                      caiq_url: null
                      status_page: https://status.emailmovers.com
                      security_txt: https://mydatahub.emailmovers.com/.well-known/security.txt
        "400":
          $ref: "#/components/responses/BadRequest"
        "429":
          $ref: "#/components/responses/RateLimited"
        default:
          $ref: "#/components/responses/ServerError"
      x-cost-credits: 0
      x-free: true
      x-anonymous: true
  /compliance/lia:
    get:
      operationId: getLia
      summary: Get the legitimate interests assessment
      description: The published LIA (EM-LIA-2026-03) with its version history.
      tags:
        - Compliance
      security: []
      parameters:
        - $ref: "#/components/parameters/MyDataHubVersion"
      responses:
        "200":
          description: The LIA.
          headers:
            RateLimit:
              $ref: "#/components/headers/RateLimit"
            RateLimit-Policy:
              $ref: "#/components/headers/RateLimit-Policy"
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/LiaDocument"
              examples:
                illustrative:
                  summary: The current LIA
                  value:
                    ref: EM-LIA-2026-03
                    version: 2026-03
                    published_at: "2026-11-30"
                    url: https://emailmovers.com/trust/lia
                    lawful_basis: legitimate_interests_art6_1_f
                    summary: Emailmovers relies on UK GDPR Article 6(1)(f) legitimate interests for named business contacts, with direct marketing as an Article 6(1)(f) example since 5 February 2026.
                    versions:
                      - ref: EM-LIA-2026-03
                        published_at: "2026-11-30"
                        url: https://emailmovers.com/trust/lia
        "400":
          $ref: "#/components/responses/BadRequest"
        "429":
          $ref: "#/components/responses/RateLimited"
        default:
          $ref: "#/components/responses/ServerError"
      x-cost-credits: 0
      x-free: true
      x-anonymous: true
  /compliance/receipts/verify:
    post:
      operationId: verifyComplianceReceipt
      summary: Verify a compliance receipt
      description: Check a receipt's JWS against the published key without an account, and learn whether the order exists, until when its licence is in force and whether it was revoked. One agent can prove to another that a list is licensed and screened. REST only.
      tags:
        - Compliance
      security: []
      parameters:
        - $ref: "#/components/parameters/MyDataHubVersion"
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/ReceiptVerifyRequest"
            examples:
              illustrative:
                summary: A valid receipt
                value:
                  jws: eyJhbGciOiJFUzI1NiIsImtpZCI6InJjcHQtMjAyNi0wOSJ9.eyJyZWNlaXB0X2lkIjoicmNwdF8wMUpCNE5BMlE1VzhaQUs4UTJWN1hOM005UiJ9.MEUCIQDexampleSignatureOnly
      responses:
        "200":
          description: The verdict.
          headers:
            RateLimit:
              $ref: "#/components/headers/RateLimit"
            RateLimit-Policy:
              $ref: "#/components/headers/RateLimit-Policy"
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ReceiptVerification"
              examples:
                illustrative:
                  summary: A valid receipt
                  value:
                    valid: true
                    order_exists: true
                    licence_in_force_until: "2027-09-26"
                    revoked: false
                    receipt_id: rcpt_01JB4NA2Q5W8ZAK8Q2V7XN3M9R
                    checked_at: "2026-10-01T09:00:00Z"
        "400":
          $ref: "#/components/responses/BadRequest"
        "422":
          $ref: "#/components/responses/ValidationFailed"
        "429":
          $ref: "#/components/responses/RateLimited"
        default:
          $ref: "#/components/responses/ServerError"
      x-cost-credits: 0
      x-free: true
      x-anonymous: true
  /account:
    get:
      operationId: getAccount
      summary: Get the account
      description: Balance by source, plan, per-action prices, the rate limit and, for an agent key, its budget and per-order cap.
      tags:
        - Account
      security:
        - OAuth2:
            - account:read
        - ApiKey:
            - account:read
      parameters:
        - $ref: "#/components/parameters/MyDataHubVersion"
      responses:
        "200":
          description: The account.
          headers:
            RateLimit:
              $ref: "#/components/headers/RateLimit"
            RateLimit-Policy:
              $ref: "#/components/headers/RateLimit-Policy"
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Account"
              examples:
                illustrative:
                  summary: Called with an agent key
                  value:
                    organisation:
                      id: org_01JA0Q2V7XN3M9R6T1Y5W8ZAK8
                      name: Example Agency Ltd
                      business_details:
                        confirmed_at: null
                        required_before_order: false
                        name_editable: false
                    user: null
                    mode: live
                    plan:
                      id: pay_as_you_go
                      name: Pay as you go
                      kind: subscription
                      renews_at: "2026-10-26"
                      cancel_at_period_end: false
                    balance:
                      credits: 6200
                      pack_credits: 5000
                      subscription_credits: 1200
                      rollover_cap: 12000
                      low_threshold: 500
                    prices:
                      - action: count
                        label: Count, breakdown, preview, machine quote, catalogue, title expansion
                        credits: 0
                        unit: call
                        status: confirmed
                      - action: record
                        label: Full UK contact record delivered, any channel or field set
                        credits: 1
                        unit: record
                        status: confirmed
                      - action: company_only_record
                        label: Company-only record (no named contact)
                        credits: 0.1
                        unit: record
                        status: proposed
                      - action: enrich_contact
                        label: Contact enrichment, match found
                        credits: 1
                        unit: match
                        status: confirmed
                        note: 0 if already licensed
                      - action: lookup_company
                        label: Company lookup, no contact details
                        credits: 0
                        unit: call
                        status: to_confirm
                      - action: outright
                        label: Outright ownership
                        credits: 1.8
                        unit: record
                        status: proposed
                    gbp_per_credit: 0.299
                    rate_limit:
                      tier: paid
                      counts_per_minute: 600
                      counts_per_hour: null
                      counts_per_day: null
                      remaining: 598
                      resets_at: "2026-09-26T10:15:00Z"
                      status: to_confirm
                    key:
                      id: key_01JB3Z9R6T1Y5W8ZAK8Q2V7XN3
                      kind: agent
                      budget:
                        monthly_credits: 1000
                        spent: 400
                        remaining: 600
                        resets_at: "2026-10-01T00:00:00Z"
                      per_order_max_credits: 500
                      expires_at: "2027-03-31T00:00:00Z"
                    account_manager:
                      name: Jordan Hale
                      calendar_url: https://emailmovers.com/talk-to-us
                    agreements:
                      dlsa:
                        name: Data Licence and Sharing Agreement
                        version: "1.0"
                        url: https://emailmovers.com/data-licence/
                        accepted:
                          version: "1.0"
                          accepted_at: "2026-11-02T10:12:00Z"
                          accepted_by: usr_01JB4H8Q2V7XN3M9R6T1Y5W8ZA
                      dpa:
                        name: Article 28 data processing agreement
                        version: "1.0"
                        url: https://emailmovers.com/dpa/
                        accepted: null
        "400":
          $ref: "#/components/responses/BadRequest"
        "401":
          $ref: "#/components/responses/Unauthorized"
        "403":
          $ref: "#/components/responses/Forbidden"
        "429":
          $ref: "#/components/responses/RateLimited"
        default:
          $ref: "#/components/responses/ServerError"
      x-cost-credits: 0
      x-free: true
      x-anonymous: false
      x-mcp-tool: get_account
    patch:
      operationId: updateAccount
      summary: Confirm the business details
      description: "Set the organisation's business name and confirm that it is a UK business. An organisation made by self-serve sign-up needs both before its first order or enrichment, free or paid: until then POST /v1/orders and POST /v1/contacts/enrich answer 403 business-details-required and charge nothing. A signed-in person who owns the account confirms them, through OAuth (with account:write, or admin) or in the app; an API key cannot. An organisation Emailmovers set up keeps the name staff gave it and needs no business details, so it is refused with 422 (organisation.business_details.name_editable is false)."
      tags:
        - Account
      security:
        - OAuth2:
            - account:write
      parameters:
        - $ref: "#/components/parameters/MyDataHubVersion"
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/AccountUpdateRequest"
            examples:
              illustrative:
                summary: Confirm the business name and that it is a UK business
                value:
                  business_name: Example Trading Ltd
                  uk_business: true
      responses:
        "200":
          description: The account.
          headers:
            RateLimit:
              $ref: "#/components/headers/RateLimit"
            RateLimit-Policy:
              $ref: "#/components/headers/RateLimit-Policy"
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Account"
              examples:
                illustrative:
                  summary: Confirm the business name and that it is a UK business
                  value:
                    organisation:
                      id: org_01JA0Q2V7XN3M9R6T1Y5W8ZAK8
                      name: Example Trading Ltd
                      business_details:
                        confirmed_at: "2026-10-18T09:30:00Z"
                        required_before_order: false
                        name_editable: true
                    user:
                      id: usr_01JA0Q3V7XN3M9R6T1Y5W8ZAK8
                      name: jo@example-trading.co.uk
                    mode: live
                    plan:
                      id: free
                      name: Free
                      kind: free
                      renews_at: null
                      cancel_at_period_end: false
                    balance:
                      credits: 100
                      pack_credits: 100
                      subscription_credits: 0
                      rollover_cap: null
                      low_threshold: null
                    prices:
                      - action: count
                        label: Count, breakdown, preview, machine quote, catalogue, title expansion
                        credits: 0
                        unit: call
                        status: confirmed
                      - action: record
                        label: Full UK contact record delivered, any channel or field set
                        credits: 1
                        unit: record
                        status: confirmed
                      - action: company_only_record
                        label: Company-only record (no named contact)
                        credits: 0.1
                        unit: record
                        status: proposed
                      - action: enrich_contact
                        label: Contact enrichment, match found
                        credits: 1
                        unit: match
                        status: confirmed
                        note: 0 if already licensed
                      - action: lookup_company
                        label: Company lookup, no contact details
                        credits: 0
                        unit: call
                        status: to_confirm
                      - action: outright
                        label: Outright ownership
                        credits: 1.8
                        unit: record
                        status: proposed
                    gbp_per_credit: 0.299
                    rate_limit:
                      tier: free
                      counts_per_minute: 60
                      counts_per_hour: null
                      counts_per_day: 1000
                      remaining: 59
                      resets_at: "2026-09-26T10:15:00Z"
                      status: to_confirm
                    key: null
                    account_manager: null
                    agreements:
                      dlsa:
                        name: Data Licence and Sharing Agreement
                        version: "1.0"
                        url: https://emailmovers.com/data-licence/
                        accepted:
                          version: "1.0"
                          accepted_at: "2026-11-02T10:12:00Z"
                          accepted_by: usr_01JB4H8Q2V7XN3M9R6T1Y5W8ZA
                      dpa:
                        name: Article 28 data processing agreement
                        version: "1.0"
                        url: https://emailmovers.com/dpa/
                        accepted: null
        "400":
          $ref: "#/components/responses/BadRequest"
        "401":
          $ref: "#/components/responses/Unauthorized"
        "403":
          $ref: "#/components/responses/Forbidden"
        "422":
          $ref: "#/components/responses/ValidationFailed"
        "429":
          $ref: "#/components/responses/RateLimited"
        default:
          $ref: "#/components/responses/ServerError"
      x-cost-credits: 0
      x-free: true
      x-anonymous: false
  /account/agreements:
    post:
      operationId: acceptAccountAgreements
      summary: Accept the data processing agreement
      description: "Accept the Article 28 data processing agreement at the version in force (the account's agreements.dpa). A suppression upload needs it first: until it is accepted POST /v1/suppressions answers 403 dpa-acceptance-required and uploads nothing, unless a person accepts it with that upload. A signed-in person who owns the account accepts it, through OAuth (with account:write, or admin) or in the app's Settings; an API key cannot. The account keeps the version, the person and the time, and a later version is accepted again."
      tags:
        - Account
      security:
        - OAuth2:
            - account:write
      parameters:
        - $ref: "#/components/parameters/MyDataHubVersion"
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/AccountAgreementsRequest"
            examples:
              illustrative:
                summary: Accept the data processing agreement
                value:
                  dpa_accepted_version: "1.0"
      responses:
        "200":
          description: The account, with its agreements.
          headers:
            RateLimit:
              $ref: "#/components/headers/RateLimit"
            RateLimit-Policy:
              $ref: "#/components/headers/RateLimit-Policy"
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Account"
              examples:
                illustrative:
                  summary: Accept the data processing agreement
                  value:
                    organisation:
                      id: org_01JA0Q2V7XN3M9R6T1Y5W8ZAK8
                      name: Example Agency Ltd
                      business_details:
                        confirmed_at: null
                        required_before_order: false
                        name_editable: false
                    user: null
                    mode: live
                    plan:
                      id: pay_as_you_go
                      name: Pay as you go
                      kind: subscription
                      renews_at: "2026-10-26"
                      cancel_at_period_end: false
                    balance:
                      credits: 6200
                      pack_credits: 5000
                      subscription_credits: 1200
                      rollover_cap: 12000
                      low_threshold: 500
                    prices:
                      - action: count
                        label: Count, breakdown, preview, machine quote, catalogue, title expansion
                        credits: 0
                        unit: call
                        status: confirmed
                      - action: record
                        label: Full UK contact record delivered, any channel or field set
                        credits: 1
                        unit: record
                        status: confirmed
                      - action: company_only_record
                        label: Company-only record (no named contact)
                        credits: 0.1
                        unit: record
                        status: proposed
                      - action: enrich_contact
                        label: Contact enrichment, match found
                        credits: 1
                        unit: match
                        status: confirmed
                        note: 0 if already licensed
                      - action: lookup_company
                        label: Company lookup, no contact details
                        credits: 0
                        unit: call
                        status: to_confirm
                      - action: outright
                        label: Outright ownership
                        credits: 1.8
                        unit: record
                        status: proposed
                    gbp_per_credit: 0.299
                    rate_limit:
                      tier: paid
                      counts_per_minute: 600
                      counts_per_hour: null
                      counts_per_day: null
                      remaining: 598
                      resets_at: "2026-09-26T10:15:00Z"
                      status: to_confirm
                    key:
                      id: key_01JB3Z9R6T1Y5W8ZAK8Q2V7XN3
                      kind: agent
                      budget:
                        monthly_credits: 1000
                        spent: 400
                        remaining: 600
                        resets_at: "2026-10-01T00:00:00Z"
                      per_order_max_credits: 500
                      expires_at: "2027-03-31T00:00:00Z"
                    account_manager:
                      name: Jordan Hale
                      calendar_url: https://emailmovers.com/talk-to-us
                    agreements:
                      dlsa:
                        name: Data Licence and Sharing Agreement
                        version: "1.0"
                        url: https://emailmovers.com/data-licence/
                        accepted:
                          version: "1.0"
                          accepted_at: "2026-11-02T10:12:00Z"
                          accepted_by: usr_01JB4H8Q2V7XN3M9R6T1Y5W8ZA
                      dpa:
                        name: Article 28 data processing agreement
                        version: "1.0"
                        url: https://emailmovers.com/dpa/
                        accepted:
                          version: "1.0"
                          accepted_at: "2026-11-03T09:00:00Z"
                          accepted_by: usr_01JB4H8Q2V7XN3M9R6T1Y5W8ZA
        "400":
          $ref: "#/components/responses/BadRequest"
        "401":
          $ref: "#/components/responses/Unauthorized"
        "403":
          $ref: "#/components/responses/Forbidden"
        "422":
          $ref: "#/components/responses/ValidationFailed"
        "429":
          $ref: "#/components/responses/RateLimited"
        default:
          $ref: "#/components/responses/ServerError"
      x-cost-credits: 0
      x-free: true
      x-anonymous: false
  /account/usage:
    get:
      operationId: getAccountUsage
      summary: Get account usage
      description: Credits charged, refunded and added in a period, by action and by key.
      tags:
        - Account
      security:
        - OAuth2:
            - account:read
        - ApiKey:
            - account:read
      parameters:
        - $ref: "#/components/parameters/MyDataHubVersion"
        - name: from
          in: query
          required: false
          description: First day, inclusive. Default 30 days ago.
          schema:
            type: string
            format: date
          example: "2026-09-01"
        - name: to
          in: query
          required: false
          description: Last day, inclusive. Default today.
          schema:
            type: string
            format: date
          example: "2026-09-30"
      responses:
        "200":
          description: Usage in the period.
          headers:
            RateLimit:
              $ref: "#/components/headers/RateLimit"
            RateLimit-Policy:
              $ref: "#/components/headers/RateLimit-Policy"
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/AccountUsage"
              examples:
                illustrative:
                  summary: September 2026
                  value:
                    from: "2026-09-01"
                    to: "2026-09-30"
                    credits_charged: 5400
                    credits_refunded: 0
                    credits_added: 6000
                    by_action:
                      - action: count
                        calls: 1840
                        credits: 0
                      - action: record
                        calls: 2
                        credits: 5400
                    by_key:
                      - key_id: key_01JB3Z9R6T1Y5W8ZAK8Q2V7XN3
                        label: SDR agent
                        credits: 400
        "400":
          $ref: "#/components/responses/BadRequest"
        "401":
          $ref: "#/components/responses/Unauthorized"
        "403":
          $ref: "#/components/responses/Forbidden"
        "422":
          $ref: "#/components/responses/ValidationFailed"
        "429":
          $ref: "#/components/responses/RateLimited"
        default:
          $ref: "#/components/responses/ServerError"
      x-cost-credits: 0
      x-free: true
      x-anonymous: false
  /account/cancel:
    post:
      operationId: cancelAccount
      summary: Cancel the plan
      description: End a rolling plan at the end of its period, in one call, with no reason required; any notice terms in your agreement still apply. Credit pack credits never expire and survive cancellation, and delivered records stay licensed to the end of their term.
      tags:
        - Account
      security:
        - OAuth2:
            - admin
        - ApiKey:
            - admin
      parameters:
        - $ref: "#/components/parameters/MyDataHubVersion"
      requestBody:
        required: false
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/AccountCancellationRequest"
            examples:
              illustrative:
                summary: Cancel pay as you go
                value:
                  reason: Campaign finished.
      responses:
        "200":
          description: When the plan ends and what is kept.
          headers:
            RateLimit:
              $ref: "#/components/headers/RateLimit"
            RateLimit-Policy:
              $ref: "#/components/headers/RateLimit-Policy"
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/AccountCancellation"
              examples:
                illustrative:
                  summary: Cancel pay as you go
                  value:
                    plan: pay_as_you_go
                    cancel_at_period_end: true
                    ends_at: "2026-10-26"
                    pack_credits_kept: 5000
                    subscription_credits_usable_until: "2026-10-26"
                    licences_unaffected: true
        "400":
          $ref: "#/components/responses/BadRequest"
        "401":
          $ref: "#/components/responses/Unauthorized"
        "403":
          $ref: "#/components/responses/Forbidden"
        "422":
          $ref: "#/components/responses/ValidationFailed"
        "429":
          $ref: "#/components/responses/RateLimited"
        default:
          $ref: "#/components/responses/ServerError"
      x-cost-credits: 0
      x-free: true
      x-anonymous: false
  /account/purchases:
    post:
      operationId: createCreditPurchase
      summary: Buy credits by card
      description: Start a card payment in Stripe Checkout for credits at the per-contact scale (the size of the line picks its band), the 40,000 credit pack, or a quote's purchase option line by line. Prices exclude VAT; 20 percent UK VAT is added at checkout, which takes cards only (Apple Pay and Google Pay through the card method) and a UK billing address. Answers with the checkout_url to send the person to. The credits are added once, when Stripe says the payment is paid. Credits and credit packs are not refundable once bought, except where the law requires. A signed-in person with the admin scope buys; an API key cannot. The subscription is not bought here. 503 when card payments are not switched on.
      tags:
        - Account
      security:
        - OAuth2:
            - admin
      parameters:
        - $ref: "#/components/parameters/MyDataHubVersion"
        - $ref: "#/components/parameters/IdempotencyKey"
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/CreditPurchaseRequest"
            examples:
              illustrative:
                summary: Buy the 40,000 credit pack
                value:
                  lines:
                    - id: credit_pack_40000
                      quantity: 1
      responses:
        "201":
          description: The pending purchase and its checkout_url.
          headers:
            RateLimit:
              $ref: "#/components/headers/RateLimit"
            RateLimit-Policy:
              $ref: "#/components/headers/RateLimit-Policy"
            Location:
              $ref: "#/components/headers/Location"
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/CreditPurchase"
              examples:
                illustrative:
                  summary: Buy the 40,000 credit pack
                  value:
                    id: pur_01JB4R7K2V7XN3M9R6T1Y5W8ZA
                    label: 1 credit pack of 40,000
                    lines:
                      - id: credit_pack_40000
                        quantity: 1
                        credits: 40000
                        gbp_ex_vat: 1249
                    credits: 40000
                    gbp_ex_vat: 1249
                    vat_rate_percent: 20
                    vat_gbp: 249.8
                    total_gbp: 1498.8
                    currency: GBP
                    gbp_per_credit: 0.031225
                    test_mode: true
                    created_at: "2026-10-16T10:00:00Z"
                    expires_at: "2026-10-17T10:00:00Z"
                    attention: null
                    status: pending
                    credits_added: 0
                    checkout_url: https://checkout.stripe.com/c/pay/cs_test_EXAMPLE
                    paid_at: null
                    receipt: null
        "400":
          $ref: "#/components/responses/BadRequest"
        "401":
          $ref: "#/components/responses/Unauthorized"
        "403":
          $ref: "#/components/responses/Forbidden"
        "409":
          $ref: "#/components/responses/Conflict"
        "422":
          $ref: "#/components/responses/ValidationFailed"
        "429":
          $ref: "#/components/responses/RateLimited"
        default:
          $ref: "#/components/responses/ServerError"
      x-cost-credits: 0
      x-free: true
      x-anonymous: false
      x-idempotency-key: optional
    get:
      operationId: listCreditPurchases
      summary: List credit purchases
      description: Credits bought by card, newest first, each with its price excluding VAT, the VAT, the total and, once paid, its payment receipt. payments says whether card payments are switched on here and whether they are Stripe test mode.
      tags:
        - Account
      security:
        - OAuth2:
            - account:read
        - ApiKey:
            - account:read
      parameters:
        - $ref: "#/components/parameters/MyDataHubVersion"
        - name: limit
          in: query
          required: false
          description: Items per page, 1 to 100. Default 25.
          schema:
            default: 25
            type: integer
            minimum: 1
            maximum: 100
        - name: cursor
          in: query
          required: false
          description: An opaque cursor from next_cursor. Pass it back unchanged.
          schema:
            type: string
            minLength: 1
            maxLength: 512
      responses:
        "200":
          description: A page of credit purchases.
          headers:
            RateLimit:
              $ref: "#/components/headers/RateLimit"
            RateLimit-Policy:
              $ref: "#/components/headers/RateLimit-Policy"
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/CreditPurchaseList"
              examples:
                illustrative:
                  summary: One paid credit pack
                  value:
                    data:
                      - id: pur_01JB4R7K2V7XN3M9R6T1Y5W8ZA
                        label: 1 credit pack of 40,000
                        lines:
                          - id: credit_pack_40000
                            quantity: 1
                            credits: 40000
                            gbp_ex_vat: 1249
                        credits: 40000
                        gbp_ex_vat: 1249
                        vat_rate_percent: 20
                        vat_gbp: 249.8
                        total_gbp: 1498.8
                        currency: GBP
                        gbp_per_credit: 0.031225
                        test_mode: true
                        created_at: "2026-10-16T10:00:00Z"
                        expires_at: "2026-10-17T10:00:00Z"
                        attention: null
                        status: paid
                        credits_added: 40000
                        checkout_url: null
                        paid_at: "2026-10-16T10:03:12Z"
                        receipt:
                          number: MDH-000042
                          issued_at: "2026-10-16T10:03:12Z"
                          supplier:
                            name: Example Supplier Limited
                            company_number: "00000000"
                            registered_office: 1 Example Street, Exampletown, EX1 1EX, England
                            vat_number: GB000000000
                          customer:
                            organisation_id: org_01JA0Q2V7XN3M9R6T1Y5W8ZAK8
                            organisation_name: Example Agency Ltd
                            business_name: Example Agency Ltd
                            name: Example Agency Ltd
                            address:
                              line1: 2 Sample Road
                              line2: null
                              city: Exampletown
                              postal_code: EX2 2EX
                              state: null
                              country: GB
                            vat_number: GB999999999
                          description: 1 credit pack of 40,000
                          credits: 40000
                          gbp_per_credit: 0.031225
                          gbp_ex_vat: 1249
                          vat_rate_percent: 20
                          vat_gbp: 249.8
                          total_gbp: 1498.8
                          paid_by: card
                          payment_reference: pi_test_EXAMPLE
                          refunds: Credits and credit packs are not refundable once bought, except where the law requires.
                          test_mode: true
                    has_more: false
                    next_cursor: null
                    payments:
                      available: true
                      test_mode: true
                      vat_rate_percent: 20
        "400":
          $ref: "#/components/responses/BadRequest"
        "401":
          $ref: "#/components/responses/Unauthorized"
        "403":
          $ref: "#/components/responses/Forbidden"
        "422":
          $ref: "#/components/responses/ValidationFailed"
        "429":
          $ref: "#/components/responses/RateLimited"
        default:
          $ref: "#/components/responses/ServerError"
      x-cost-credits: 0
      x-free: true
      x-anonymous: false
  /account/purchases/{id}:
    get:
      operationId: getCreditPurchase
      summary: Get a credit purchase
      description: One purchase of credits by card. Reading a pending purchase asks Stripe for its checkout and, once the payment is paid, adds the credits exactly once and writes the payment receipt, so the page Checkout returns to can show the credits added. Until then it stays pending. 503 when card payments are switched off and the purchase is still pending.
      tags:
        - Account
      security:
        - OAuth2:
            - account:read
        - ApiKey:
            - account:read
      parameters:
        - $ref: "#/components/parameters/MyDataHubVersion"
        - name: id
          in: path
          required: true
          description: "Format: a credit purchase id: pur_ followed by a 26 character ULID in upper case Crockford base32, such as pur_01JB4R7K2V7XN3M9R6T1Y5W8ZA."
          schema:
            type: string
            pattern: ^pur_[0-7][0-9A-HJKMNP-TV-Z]{25}$
            examples:
              - pur_01JB4R7K2V7XN3M9R6T1Y5W8ZA
          example: pur_01JB4R7K2V7XN3M9R6T1Y5W8ZA
      responses:
        "200":
          description: The purchase.
          headers:
            RateLimit:
              $ref: "#/components/headers/RateLimit"
            RateLimit-Policy:
              $ref: "#/components/headers/RateLimit-Policy"
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/CreditPurchase"
              examples:
                illustrative:
                  summary: Paid, with its receipt
                  value:
                    id: pur_01JB4R7K2V7XN3M9R6T1Y5W8ZA
                    label: 1 credit pack of 40,000
                    lines:
                      - id: credit_pack_40000
                        quantity: 1
                        credits: 40000
                        gbp_ex_vat: 1249
                    credits: 40000
                    gbp_ex_vat: 1249
                    vat_rate_percent: 20
                    vat_gbp: 249.8
                    total_gbp: 1498.8
                    currency: GBP
                    gbp_per_credit: 0.031225
                    test_mode: true
                    created_at: "2026-10-16T10:00:00Z"
                    expires_at: "2026-10-17T10:00:00Z"
                    attention: null
                    status: paid
                    credits_added: 40000
                    checkout_url: null
                    paid_at: "2026-10-16T10:03:12Z"
                    receipt:
                      number: MDH-000042
                      issued_at: "2026-10-16T10:03:12Z"
                      supplier:
                        name: Example Supplier Limited
                        company_number: "00000000"
                        registered_office: 1 Example Street, Exampletown, EX1 1EX, England
                        vat_number: GB000000000
                      customer:
                        organisation_id: org_01JA0Q2V7XN3M9R6T1Y5W8ZAK8
                        organisation_name: Example Agency Ltd
                        business_name: Example Agency Ltd
                        name: Example Agency Ltd
                        address:
                          line1: 2 Sample Road
                          line2: null
                          city: Exampletown
                          postal_code: EX2 2EX
                          state: null
                          country: GB
                        vat_number: GB999999999
                      description: 1 credit pack of 40,000
                      credits: 40000
                      gbp_per_credit: 0.031225
                      gbp_ex_vat: 1249
                      vat_rate_percent: 20
                      vat_gbp: 249.8
                      total_gbp: 1498.8
                      paid_by: card
                      payment_reference: pi_test_EXAMPLE
                      refunds: Credits and credit packs are not refundable once bought, except where the law requires.
                      test_mode: true
        "400":
          $ref: "#/components/responses/BadRequest"
        "401":
          $ref: "#/components/responses/Unauthorized"
        "403":
          $ref: "#/components/responses/Forbidden"
        "404":
          $ref: "#/components/responses/NotFound"
        "429":
          $ref: "#/components/responses/RateLimited"
        default:
          $ref: "#/components/responses/ServerError"
      x-cost-credits: 0
      x-free: true
      x-anonymous: false
  /api-keys:
    get:
      operationId: listApiKeys
      summary: List API keys
      description: API keys and agent keys, with each agent key's live spend meter. Secrets are never shown again.
      tags:
        - Keys
      security:
        - OAuth2:
            - admin
        - ApiKey:
            - admin
      parameters:
        - $ref: "#/components/parameters/MyDataHubVersion"
        - name: limit
          in: query
          required: false
          description: Items per page, 1 to 100. Default 25.
          schema:
            default: 25
            type: integer
            minimum: 1
            maximum: 100
        - name: cursor
          in: query
          required: false
          description: An opaque cursor from next_cursor. Pass it back unchanged.
          schema:
            type: string
            minLength: 1
            maxLength: 512
      responses:
        "200":
          description: A page of keys.
          headers:
            RateLimit:
              $ref: "#/components/headers/RateLimit"
            RateLimit-Policy:
              $ref: "#/components/headers/RateLimit-Policy"
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ApiKeyList"
              examples:
                illustrative:
                  summary: One agent key
                  value:
                    data:
                      - id: key_01JB3Z9R6T1Y5W8ZAK8Q2V7XN3
                        kind: agent
                        label: SDR agent
                        mode: live
                        prefix: mdh_live_7Kq2
                        scopes:
                          - counts:read
                          - quotes:read
                          - orders:write
                          - orders:read
                          - records:read
                          - pushes:write
                        ip_allowlist: []
                        created_at: "2026-09-20T09:00:00Z"
                        created_by: usr_01JA0Q3V7XN3M9R6T1Y5W8ZAK8
                        last_used_at: "2026-09-26T02:00:00Z"
                        expires_at: "2027-03-31T00:00:00Z"
                        revoked_at: null
                        allowed_tools:
                          - count_audience
                          - quote_order
                          - create_order
                          - fetch_order_records
                          - push_to_crm
                        allowed_connection_ids:
                          - conn_01JB3Y2X6Y7Z8A1B2C3D4E5F6G
                        monthly_budget_credits: 1000
                        per_order_max_credits: 500
                        budget:
                          monthly_credits: 1000
                          spent: 400
                          remaining: 600
                          resets_at: "2026-10-01T00:00:00Z"
                        webhook_url: https://agent.example.com/mydatahub/events
                        disclosure: account
                    has_more: false
                    next_cursor: null
        "400":
          $ref: "#/components/responses/BadRequest"
        "401":
          $ref: "#/components/responses/Unauthorized"
        "403":
          $ref: "#/components/responses/Forbidden"
        "422":
          $ref: "#/components/responses/ValidationFailed"
        "429":
          $ref: "#/components/responses/RateLimited"
        default:
          $ref: "#/components/responses/ServerError"
      x-cost-credits: 0
      x-free: true
      x-anonymous: false
    post:
      operationId: createApiKey
      summary: Create an API key
      description: Create a standard key or an agent key. An agent key has a label, allowed tools, a monthly budget, a per-order cap, an expiry and a webhook, and is created by a signed-in person through OAuth, never by a key. An agent key needs allowed_tools and per_order_max_credits and never takes admin. Unlimited keys on every plan; the secret is shown once.
      tags:
        - Keys
      security:
        - OAuth2:
            - admin
      parameters:
        - $ref: "#/components/parameters/MyDataHubVersion"
        - $ref: "#/components/parameters/IdempotencyKey"
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/ApiKeyRequest"
            examples:
              illustrative:
                summary: An agent key with a 1,000 credit monthly budget
                value:
                  kind: agent
                  label: SDR agent
                  scopes:
                    - counts:read
                    - quotes:read
                    - orders:write
                    - orders:read
                    - records:read
                    - pushes:write
                  allowed_tools:
                    - count_audience
                    - quote_order
                    - create_order
                    - fetch_order_records
                    - push_to_crm
                  allowed_connection_ids:
                    - conn_01JB3Y2X6Y7Z8A1B2C3D4E5F6G
                  monthly_budget_credits: 1000
                  per_order_max_credits: 500
                  expires_at: "2027-03-31T00:00:00Z"
                  licence_accepted_version: "1.0"
      responses:
        "201":
          description: The key and its secret.
          headers:
            RateLimit:
              $ref: "#/components/headers/RateLimit"
            RateLimit-Policy:
              $ref: "#/components/headers/RateLimit-Policy"
            Location:
              $ref: "#/components/headers/Location"
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ApiKeyCreated"
              examples:
                illustrative:
                  summary: An agent key with a 1,000 credit monthly budget
                  value:
                    key:
                      id: key_01JB3Z9R6T1Y5W8ZAK8Q2V7XN3
                      kind: agent
                      label: SDR agent
                      mode: test
                      prefix: mdh_test_Exam
                      scopes:
                        - counts:read
                        - quotes:read
                        - orders:write
                        - orders:read
                        - records:read
                        - pushes:write
                      ip_allowlist: []
                      created_at: "2026-09-20T09:00:00Z"
                      created_by: usr_01JA0Q3V7XN3M9R6T1Y5W8ZAK8
                      last_used_at: "2026-09-26T02:00:00Z"
                      expires_at: "2027-03-31T00:00:00Z"
                      revoked_at: null
                      allowed_tools:
                        - count_audience
                        - quote_order
                        - create_order
                        - fetch_order_records
                        - push_to_crm
                      allowed_connection_ids:
                        - conn_01JB3Y2X6Y7Z8A1B2C3D4E5F6G
                      monthly_budget_credits: 1000
                      per_order_max_credits: 500
                      budget:
                        monthly_credits: 1000
                        spent: 400
                        remaining: 600
                        resets_at: "2026-10-01T00:00:00Z"
                      webhook_url: https://agent.example.com/mydatahub/events
                      disclosure: account
                    secret: mdh_test_Example0000000000000000000000000
        "400":
          $ref: "#/components/responses/BadRequest"
        "401":
          $ref: "#/components/responses/Unauthorized"
        "403":
          $ref: "#/components/responses/Forbidden"
        "409":
          $ref: "#/components/responses/Conflict"
        "422":
          $ref: "#/components/responses/ValidationFailed"
        "429":
          $ref: "#/components/responses/RateLimited"
        default:
          $ref: "#/components/responses/ServerError"
      x-cost-credits: 0
      x-free: true
      x-anonymous: false
      x-idempotency-key: optional
  /api-keys/{id}:
    patch:
      operationId: updateApiKey
      summary: Update an API key
      description: "Change a key's label, scopes, allowlist, expiry or, for an agent key, its tools, connections, budget and cap. Only a signed-in person, through OAuth, may: no key changes its own limits, and an agent key never takes admin."
      tags:
        - Keys
      security:
        - OAuth2:
            - admin
      parameters:
        - $ref: "#/components/parameters/MyDataHubVersion"
        - name: id
          in: path
          required: true
          description: "Format: an API key id: key_ followed by a 26 character ULID in upper case Crockford base32, such as key_01JB3Z4Q2V7XN3M9R6T1Y5W8ZA."
          schema:
            type: string
            pattern: ^key_[0-7][0-9A-HJKMNP-TV-Z]{25}$
            examples:
              - key_01JB3Z4Q2V7XN3M9R6T1Y5W8ZA
          example: key_01JB3Z9R6T1Y5W8ZAK8Q2V7XN3
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/ApiKeyUpdate"
            examples:
              illustrative:
                summary: Raise an agent key budget
                value:
                  monthly_budget_credits: 2000
      responses:
        "200":
          description: The updated key.
          headers:
            RateLimit:
              $ref: "#/components/headers/RateLimit"
            RateLimit-Policy:
              $ref: "#/components/headers/RateLimit-Policy"
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ApiKey"
              examples:
                illustrative:
                  summary: Raise an agent key budget
                  value:
                    id: key_01JB3Z9R6T1Y5W8ZAK8Q2V7XN3
                    kind: agent
                    label: SDR agent
                    mode: live
                    prefix: mdh_live_7Kq2
                    scopes:
                      - counts:read
                      - quotes:read
                      - orders:write
                      - orders:read
                      - records:read
                      - pushes:write
                    ip_allowlist: []
                    created_at: "2026-09-20T09:00:00Z"
                    created_by: usr_01JA0Q3V7XN3M9R6T1Y5W8ZAK8
                    last_used_at: "2026-09-26T02:00:00Z"
                    expires_at: "2027-03-31T00:00:00Z"
                    revoked_at: null
                    allowed_tools:
                      - count_audience
                      - quote_order
                      - create_order
                      - fetch_order_records
                      - push_to_crm
                    allowed_connection_ids:
                      - conn_01JB3Y2X6Y7Z8A1B2C3D4E5F6G
                    monthly_budget_credits: 2000
                    per_order_max_credits: 500
                    budget:
                      monthly_credits: 2000
                      spent: 400
                      remaining: 1600
                      resets_at: "2026-10-01T00:00:00Z"
                    webhook_url: https://agent.example.com/mydatahub/events
                    disclosure: account
        "400":
          $ref: "#/components/responses/BadRequest"
        "401":
          $ref: "#/components/responses/Unauthorized"
        "403":
          $ref: "#/components/responses/Forbidden"
        "404":
          $ref: "#/components/responses/NotFound"
        "422":
          $ref: "#/components/responses/ValidationFailed"
        "429":
          $ref: "#/components/responses/RateLimited"
        default:
          $ref: "#/components/responses/ServerError"
      x-cost-credits: 0
      x-free: true
      x-anonymous: false
    delete:
      operationId: revokeApiKey
      summary: Revoke an API key
      description: Revoke a key at once. Orders it placed stay in the audit trail. A signed-in person revokes keys through OAuth.
      tags:
        - Keys
      security:
        - OAuth2:
            - admin
      parameters:
        - $ref: "#/components/parameters/MyDataHubVersion"
        - name: id
          in: path
          required: true
          description: "Format: an API key id: key_ followed by a 26 character ULID in upper case Crockford base32, such as key_01JB3Z4Q2V7XN3M9R6T1Y5W8ZA."
          schema:
            type: string
            pattern: ^key_[0-7][0-9A-HJKMNP-TV-Z]{25}$
            examples:
              - key_01JB3Z4Q2V7XN3M9R6T1Y5W8ZA
          example: key_01JB3Z9R6T1Y5W8ZAK8Q2V7XN3
      responses:
        "204":
          description: Revoked.
          headers:
            RateLimit:
              $ref: "#/components/headers/RateLimit"
            RateLimit-Policy:
              $ref: "#/components/headers/RateLimit-Policy"
        "400":
          $ref: "#/components/responses/BadRequest"
        "401":
          $ref: "#/components/responses/Unauthorized"
        "403":
          $ref: "#/components/responses/Forbidden"
        "404":
          $ref: "#/components/responses/NotFound"
        "429":
          $ref: "#/components/responses/RateLimited"
        default:
          $ref: "#/components/responses/ServerError"
      x-cost-credits: 0
      x-free: true
      x-anonymous: false
  /connections:
    get:
      operationId: listConnections
      summary: List CRM connections
      description: CRM and destination connections with their status.
      tags:
        - CRM push
      security:
        - OAuth2:
            - pushes:write
        - ApiKey:
            - pushes:write
      parameters:
        - $ref: "#/components/parameters/MyDataHubVersion"
        - name: limit
          in: query
          required: false
          description: Items per page, 1 to 100. Default 25.
          schema:
            default: 25
            type: integer
            minimum: 1
            maximum: 100
        - name: cursor
          in: query
          required: false
          description: An opaque cursor from next_cursor. Pass it back unchanged.
          schema:
            type: string
            minLength: 1
            maxLength: 512
      responses:
        "200":
          description: A page of connections.
          headers:
            RateLimit:
              $ref: "#/components/headers/RateLimit"
            RateLimit-Policy:
              $ref: "#/components/headers/RateLimit-Policy"
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ConnectionList"
              examples:
                illustrative:
                  summary: One HubSpot connection
                  value:
                    data:
                      - id: conn_01JB3Y2X6Y7Z8A1B2C3D4E5F6G
                        provider: hubspot
                        name: Sales HubSpot
                        status: connected
                        authorise_url: null
                        read_only_overlap: true
                        created_at: "2026-09-20T09:30:00Z"
                        last_used_at: "2026-09-26T02:05:00Z"
                    has_more: false
                    next_cursor: null
        "400":
          $ref: "#/components/responses/BadRequest"
        "401":
          $ref: "#/components/responses/Unauthorized"
        "403":
          $ref: "#/components/responses/Forbidden"
        "422":
          $ref: "#/components/responses/ValidationFailed"
        "429":
          $ref: "#/components/responses/RateLimited"
        default:
          $ref: "#/components/responses/ServerError"
      x-cost-credits: 0
      x-free: true
      x-anonymous: false
    post:
      operationId: createConnection
      summary: Create a CRM connection
      description: Start a connection to HubSpot, Salesforce, Google Sheets, Deliverite, a webhook or CSV. OAuth providers return an authorise_url for a person to approve in the app.
      tags:
        - CRM push
      security:
        - OAuth2:
            - pushes:write
        - ApiKey:
            - pushes:write
      parameters:
        - $ref: "#/components/parameters/MyDataHubVersion"
        - $ref: "#/components/parameters/IdempotencyKey"
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/ConnectionRequest"
            examples:
              illustrative:
                summary: Connect HubSpot
                value:
                  provider: hubspot
                  name: Sales HubSpot
      responses:
        "201":
          description: The connection.
          headers:
            RateLimit:
              $ref: "#/components/headers/RateLimit"
            RateLimit-Policy:
              $ref: "#/components/headers/RateLimit-Policy"
            Location:
              $ref: "#/components/headers/Location"
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Connection"
              examples:
                illustrative:
                  summary: Connect HubSpot
                  value:
                    id: conn_01JB3Y2X6Y7Z8A1B2C3D4E5F6G
                    provider: hubspot
                    name: Sales HubSpot
                    status: pending_authorisation
                    authorise_url: https://mydatahub.emailmovers.com/connections/conn_01JB3Y2X6Y7Z8A1B2C3D4E5F6G/authorise
                    read_only_overlap: true
                    created_at: "2026-09-20T09:30:00Z"
                    last_used_at: null
        "400":
          $ref: "#/components/responses/BadRequest"
        "401":
          $ref: "#/components/responses/Unauthorized"
        "403":
          $ref: "#/components/responses/Forbidden"
        "409":
          $ref: "#/components/responses/Conflict"
        "422":
          $ref: "#/components/responses/ValidationFailed"
        "429":
          $ref: "#/components/responses/RateLimited"
        default:
          $ref: "#/components/responses/ServerError"
      x-cost-credits: 0
      x-free: true
      x-anonymous: false
      x-idempotency-key: optional
  /connections/{id}/overlap:
    post:
      operationId: checkConnectionOverlap
      summary: Check CRM overlap
      description: "The New to you check before an order: read-only, email-only, salted SHA-256 matching in memory, nothing retained and nothing added to our database. Matched records are never charged."
      tags:
        - CRM push
      security:
        - OAuth2:
            - pushes:write
        - ApiKey:
            - pushes:write
      parameters:
        - $ref: "#/components/parameters/MyDataHubVersion"
        - name: id
          in: path
          required: true
          description: "Format: a CRM connection id: conn_ followed by a 26 character ULID in upper case Crockford base32, such as conn_01JB3Y2X6Y7Z8A1B2C3D4E5F6G."
          schema:
            type: string
            pattern: ^conn_[0-7][0-9A-HJKMNP-TV-Z]{25}$
            examples:
              - conn_01JB3Y2X6Y7Z8A1B2C3D4E5F6G
          example: conn_01JB3Y2X6Y7Z8A1B2C3D4E5F6G
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/OverlapRequest"
            examples:
              illustrative:
                summary: Overlap with HubSpot
                value:
                  count_id: cnt_7K2PQ4MAXN3R6TBY5WDZ
      responses:
        "200":
          description: The overlap.
          headers:
            RateLimit:
              $ref: "#/components/headers/RateLimit"
            RateLimit-Policy:
              $ref: "#/components/headers/RateLimit-Policy"
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Overlap"
              examples:
                illustrative:
                  summary: Overlap with HubSpot
                  value:
                    connection_id: conn_01JB3Y2X6Y7Z8A1B2C3D4E5F6G
                    count_id: cnt_7K2PQ4MAXN3R6TBY5WDZ
                    records: 4650
                    already_held: 548
                    new_to_you: 4102
                    method: salted_sha256_in_memory
                    retained: false
                    checked_at: "2026-09-26T10:12:00Z"
        "400":
          $ref: "#/components/responses/BadRequest"
        "401":
          $ref: "#/components/responses/Unauthorized"
        "403":
          $ref: "#/components/responses/Forbidden"
        "404":
          $ref: "#/components/responses/NotFound"
        "422":
          $ref: "#/components/responses/ValidationFailed"
        "429":
          $ref: "#/components/responses/RateLimited"
        default:
          $ref: "#/components/responses/ServerError"
      x-cost-credits: 0
      x-free: true
      x-anonymous: false
  /orders/{id}/pushes:
    post:
      operationId: createPush
      summary: Push an order to a CRM
      description: "Push an order's records to a connection, keyed on mydatahub_record_id with the MyDataHub property group. dry_run defaults to true and reports what would change. A live push needs approval: a person's confirmation_token (or MCP elicitation), or an agent key whose allowed_tools include push_to_crm and whose allowed_connection_ids include the connection; otherwise 403 confirmation-required. Records only: nothing is ever sent to the contacts. Free."
      tags:
        - CRM push
      security:
        - OAuth2:
            - pushes:write
        - ApiKey:
            - pushes:write
      parameters:
        - $ref: "#/components/parameters/MyDataHubVersion"
        - $ref: "#/components/parameters/IdempotencyKey"
        - name: id
          in: path
          required: true
          description: "Format: an order id: ord_ followed by a 26 character ULID in upper case Crockford base32, such as ord_01JB4N9S3W2X6Y7Z8A1B2C3D4E."
          schema:
            type: string
            pattern: ^ord_[0-7][0-9A-HJKMNP-TV-Z]{25}$
            examples:
              - ord_01JB4N9S3W2X6Y7Z8A1B2C3D4E
          example: ord_01JB4N9S3W2X6Y7Z8A1B2C3D4E
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/PushRequest"
            examples:
              illustrative:
                summary: Dry run to HubSpot
                value:
                  connection_id: conn_01JB3Y2X6Y7Z8A1B2C3D4E5F6G
      responses:
        "202":
          description: The push, or its dry run.
          headers:
            RateLimit:
              $ref: "#/components/headers/RateLimit"
            RateLimit-Policy:
              $ref: "#/components/headers/RateLimit-Policy"
            Location:
              $ref: "#/components/headers/Location"
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Push"
              examples:
                illustrative:
                  summary: Dry run to HubSpot
                  value:
                    id: psh_01JB4NC3W2X6Y7Z8A1B2C3D4E5
                    order_id: ord_01JB4N9S3W2X6Y7Z8A1B2C3D4E
                    connection_id: conn_01JB3Y2X6Y7Z8A1B2C3D4E5F6G
                    dry_run: true
                    status: completed
                    summary:
                      to_create: 4102
                      to_update: 0
                      already_present: 0
                      skipped: 0
                      failed: 0
                    property_group:
                      - mydatahub_record_id
                      - licence_expires_at
                      - lawful_basis
                      - article14_status
                      - tps_ctps_screened_at
                      - subscriber_class
                      - suppression_status
                      - compliance_receipt_url
                    sample:
                      mydatahub_record_id: contact_01JA9X2V7XN3M9R6T1Y5W8ZAK8
                      licence_expires_at: "2027-09-26"
                      lawful_basis: legitimate_interests_art6_1_f
                      article14_status: notified
                      tps_ctps_screened_at: "2026-09-12"
                      subscriber_class: ltd
                      suppression_status: none
                      compliance_receipt_url: https://mydatahub.emailmovers.com/receipts/rcpt_01JB4NA2Q5W8ZAK8Q2V7XN3M9R
                    errors: []
                    created_at: "2026-09-26T10:20:00Z"
                    completed_at: "2026-09-26T10:20:03Z"
        "400":
          $ref: "#/components/responses/BadRequest"
        "401":
          $ref: "#/components/responses/Unauthorized"
        "403":
          $ref: "#/components/responses/Forbidden"
        "404":
          $ref: "#/components/responses/NotFound"
        "409":
          $ref: "#/components/responses/Conflict"
        "422":
          $ref: "#/components/responses/ValidationFailed"
        "429":
          $ref: "#/components/responses/RateLimited"
        default:
          $ref: "#/components/responses/ServerError"
      x-cost-credits: 0
      x-free: true
      x-anonymous: false
      x-idempotency-key: optional
      x-mcp-tool: push_to_crm
  /pushes/{id}:
    get:
      operationId: getPush
      summary: Get a push
      description: "A push's status and summary: to create, to update, already present, skipped and failed."
      tags:
        - CRM push
      security:
        - OAuth2:
            - orders:read
        - ApiKey:
            - orders:read
      parameters:
        - $ref: "#/components/parameters/MyDataHubVersion"
        - name: id
          in: path
          required: true
          description: "Format: a CRM push id: psh_ followed by a 26 character ULID in upper case Crockford base32, such as psh_01JB4NC3W2X6Y7Z8A1B2C3D4E5."
          schema:
            type: string
            pattern: ^psh_[0-7][0-9A-HJKMNP-TV-Z]{25}$
            examples:
              - psh_01JB4NC3W2X6Y7Z8A1B2C3D4E5
          example: psh_01JB4NC3W2X6Y7Z8A1B2C3D4E5
      responses:
        "200":
          description: The push.
          headers:
            RateLimit:
              $ref: "#/components/headers/RateLimit"
            RateLimit-Policy:
              $ref: "#/components/headers/RateLimit-Policy"
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Push"
              examples:
                illustrative:
                  summary: A finished dry run
                  value:
                    id: psh_01JB4NC3W2X6Y7Z8A1B2C3D4E5
                    order_id: ord_01JB4N9S3W2X6Y7Z8A1B2C3D4E
                    connection_id: conn_01JB3Y2X6Y7Z8A1B2C3D4E5F6G
                    dry_run: true
                    status: completed
                    summary:
                      to_create: 4102
                      to_update: 0
                      already_present: 0
                      skipped: 0
                      failed: 0
                    property_group:
                      - mydatahub_record_id
                      - licence_expires_at
                      - lawful_basis
                      - article14_status
                      - tps_ctps_screened_at
                      - subscriber_class
                      - suppression_status
                      - compliance_receipt_url
                    sample:
                      mydatahub_record_id: contact_01JA9X2V7XN3M9R6T1Y5W8ZAK8
                      licence_expires_at: "2027-09-26"
                      lawful_basis: legitimate_interests_art6_1_f
                      article14_status: notified
                      tps_ctps_screened_at: "2026-09-12"
                      subscriber_class: ltd
                      suppression_status: none
                      compliance_receipt_url: https://mydatahub.emailmovers.com/receipts/rcpt_01JB4NA2Q5W8ZAK8Q2V7XN3M9R
                    errors: []
                    created_at: "2026-09-26T10:20:00Z"
                    completed_at: "2026-09-26T10:20:03Z"
        "400":
          $ref: "#/components/responses/BadRequest"
        "401":
          $ref: "#/components/responses/Unauthorized"
        "403":
          $ref: "#/components/responses/Forbidden"
        "404":
          $ref: "#/components/responses/NotFound"
        "429":
          $ref: "#/components/responses/RateLimited"
        default:
          $ref: "#/components/responses/ServerError"
      x-cost-credits: 0
      x-free: true
      x-anonymous: false
  /pushes/{id}/retry:
    post:
      operationId: retryPush
      summary: Retry a push
      description: Retry the failed records of a partial or failed push. A live retry needs the same approval as the push it retries.
      tags:
        - CRM push
      security:
        - OAuth2:
            - pushes:write
        - ApiKey:
            - pushes:write
      parameters:
        - $ref: "#/components/parameters/MyDataHubVersion"
        - $ref: "#/components/parameters/IdempotencyKey"
        - name: id
          in: path
          required: true
          description: "Format: a CRM push id: psh_ followed by a 26 character ULID in upper case Crockford base32, such as psh_01JB4NC3W2X6Y7Z8A1B2C3D4E5."
          schema:
            type: string
            pattern: ^psh_[0-7][0-9A-HJKMNP-TV-Z]{25}$
            examples:
              - psh_01JB4NC3W2X6Y7Z8A1B2C3D4E5
          example: psh_01JB4NC3W2X6Y7Z8A1B2C3D4E5
      responses:
        "202":
          description: The push, queued again.
          headers:
            RateLimit:
              $ref: "#/components/headers/RateLimit"
            RateLimit-Policy:
              $ref: "#/components/headers/RateLimit-Policy"
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Push"
              examples:
                illustrative:
                  summary: Retry a partial push
                  value:
                    id: psh_01JB4NC3W2X6Y7Z8A1B2C3D4E5
                    order_id: ord_01JB4N9S3W2X6Y7Z8A1B2C3D4E
                    connection_id: conn_01JB3Y2X6Y7Z8A1B2C3D4E5F6G
                    dry_run: false
                    status: queued
                    summary:
                      to_create: 4102
                      to_update: 0
                      already_present: 0
                      skipped: 0
                      failed: 0
                    property_group:
                      - mydatahub_record_id
                      - licence_expires_at
                      - lawful_basis
                      - article14_status
                      - tps_ctps_screened_at
                      - subscriber_class
                      - suppression_status
                      - compliance_receipt_url
                    sample:
                      mydatahub_record_id: contact_01JA9X2V7XN3M9R6T1Y5W8ZAK8
                      licence_expires_at: "2027-09-26"
                      lawful_basis: legitimate_interests_art6_1_f
                      article14_status: notified
                      tps_ctps_screened_at: "2026-09-12"
                      subscriber_class: ltd
                      suppression_status: none
                      compliance_receipt_url: https://mydatahub.emailmovers.com/receipts/rcpt_01JB4NA2Q5W8ZAK8Q2V7XN3M9R
                    errors: []
                    created_at: "2026-09-26T10:20:00Z"
                    completed_at: null
        "400":
          $ref: "#/components/responses/BadRequest"
        "401":
          $ref: "#/components/responses/Unauthorized"
        "403":
          $ref: "#/components/responses/Forbidden"
        "404":
          $ref: "#/components/responses/NotFound"
        "409":
          $ref: "#/components/responses/Conflict"
        "429":
          $ref: "#/components/responses/RateLimited"
        default:
          $ref: "#/components/responses/ServerError"
      x-cost-credits: 0
      x-free: true
      x-anonymous: false
      x-idempotency-key: optional
  /webhooks:
    get:
      operationId: listWebhookEndpoints
      summary: List webhook endpoints
      description: Webhook endpoints, their events and pinned MyDataHub-Version.
      tags:
        - Webhooks
      security:
        - OAuth2:
            - webhooks:write
        - ApiKey:
            - webhooks:write
      parameters:
        - $ref: "#/components/parameters/MyDataHubVersion"
        - name: limit
          in: query
          required: false
          description: Items per page, 1 to 100. Default 25.
          schema:
            default: 25
            type: integer
            minimum: 1
            maximum: 100
        - name: cursor
          in: query
          required: false
          description: An opaque cursor from next_cursor. Pass it back unchanged.
          schema:
            type: string
            minLength: 1
            maxLength: 512
      responses:
        "200":
          description: A page of endpoints.
          headers:
            RateLimit:
              $ref: "#/components/headers/RateLimit"
            RateLimit-Policy:
              $ref: "#/components/headers/RateLimit-Policy"
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/WebhookEndpointList"
              examples:
                illustrative:
                  summary: One endpoint
                  value:
                    data:
                      - id: whk_01JB3Y8Q2V7XN3M9R6T1Y5W8ZA
                        url: https://agent.example.com/mydatahub/events
                        events:
                          - order.ready
                          - credits.low
                          - suppression.created
                        api_version: "2027-01-15"
                        status: active
                        description: SDR agent
                        created_at: "2026-09-20T09:45:00Z"
                    has_more: false
                    next_cursor: null
        "400":
          $ref: "#/components/responses/BadRequest"
        "401":
          $ref: "#/components/responses/Unauthorized"
        "403":
          $ref: "#/components/responses/Forbidden"
        "422":
          $ref: "#/components/responses/ValidationFailed"
        "429":
          $ref: "#/components/responses/RateLimited"
        default:
          $ref: "#/components/responses/ServerError"
      x-cost-credits: 0
      x-free: true
      x-anonymous: false
    post:
      operationId: createWebhookEndpoint
      summary: Create a webhook endpoint
      description: Register an https endpoint for events. Deliveries follow Standard Webhooks (webhook-id, webhook-timestamp, webhook-signature; HMAC-SHA256 with a whsec_ secret, shown once) and retry over 24 hours. The endpoint is pinned to the request's MyDataHub-Version.
      tags:
        - Webhooks
      security:
        - OAuth2:
            - webhooks:write
        - ApiKey:
            - webhooks:write
      parameters:
        - $ref: "#/components/parameters/MyDataHubVersion"
        - $ref: "#/components/parameters/IdempotencyKey"
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/WebhookEndpointRequest"
            examples:
              illustrative:
                summary: Order and credit events
                value:
                  url: https://agent.example.com/mydatahub/events
                  events:
                    - order.ready
                    - credits.low
                    - suppression.created
                  description: SDR agent
      responses:
        "201":
          description: The endpoint and its secret.
          headers:
            RateLimit:
              $ref: "#/components/headers/RateLimit"
            RateLimit-Policy:
              $ref: "#/components/headers/RateLimit-Policy"
            Location:
              $ref: "#/components/headers/Location"
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/WebhookEndpointCreated"
              examples:
                illustrative:
                  summary: Order and credit events
                  value:
                    endpoint:
                      id: whk_01JB3Y8Q2V7XN3M9R6T1Y5W8ZA
                      url: https://agent.example.com/mydatahub/events
                      events:
                        - order.ready
                        - credits.low
                        - suppression.created
                      api_version: "2027-01-15"
                      status: active
                      description: SDR agent
                      created_at: "2026-09-20T09:45:00Z"
                    secret: whsec_ExampleSecret0000000000000000000
        "400":
          $ref: "#/components/responses/BadRequest"
        "401":
          $ref: "#/components/responses/Unauthorized"
        "403":
          $ref: "#/components/responses/Forbidden"
        "409":
          $ref: "#/components/responses/Conflict"
        "422":
          $ref: "#/components/responses/ValidationFailed"
        "429":
          $ref: "#/components/responses/RateLimited"
        default:
          $ref: "#/components/responses/ServerError"
      x-cost-credits: 0
      x-free: true
      x-anonymous: false
      x-idempotency-key: optional
  /webhooks/{id}:
    delete:
      operationId: deleteWebhookEndpoint
      summary: Delete a webhook endpoint
      description: Stop deliveries to an endpoint. Its events stay in the event log for 30 days.
      tags:
        - Webhooks
      security:
        - OAuth2:
            - webhooks:write
        - ApiKey:
            - webhooks:write
      parameters:
        - $ref: "#/components/parameters/MyDataHubVersion"
        - name: id
          in: path
          required: true
          description: "Format: a webhook endpoint id: whk_ followed by a 26 character ULID in upper case Crockford base32, such as whk_01JB3Y8Q2V7XN3M9R6T1Y5W8ZA."
          schema:
            type: string
            pattern: ^whk_[0-7][0-9A-HJKMNP-TV-Z]{25}$
            examples:
              - whk_01JB3Y8Q2V7XN3M9R6T1Y5W8ZA
          example: whk_01JB3Y8Q2V7XN3M9R6T1Y5W8ZA
      responses:
        "204":
          description: Deleted.
          headers:
            RateLimit:
              $ref: "#/components/headers/RateLimit"
            RateLimit-Policy:
              $ref: "#/components/headers/RateLimit-Policy"
        "400":
          $ref: "#/components/responses/BadRequest"
        "401":
          $ref: "#/components/responses/Unauthorized"
        "403":
          $ref: "#/components/responses/Forbidden"
        "404":
          $ref: "#/components/responses/NotFound"
        "429":
          $ref: "#/components/responses/RateLimited"
        default:
          $ref: "#/components/responses/ServerError"
      x-cost-credits: 0
      x-free: true
      x-anonymous: false
  /webhooks/{id}/test:
    post:
      operationId: testWebhookEndpoint
      summary: Send a test event
      description: Send a signed test event to an endpoint and report the response.
      tags:
        - Webhooks
      security:
        - OAuth2:
            - webhooks:write
        - ApiKey:
            - webhooks:write
      parameters:
        - $ref: "#/components/parameters/MyDataHubVersion"
        - name: id
          in: path
          required: true
          description: "Format: a webhook endpoint id: whk_ followed by a 26 character ULID in upper case Crockford base32, such as whk_01JB3Y8Q2V7XN3M9R6T1Y5W8ZA."
          schema:
            type: string
            pattern: ^whk_[0-7][0-9A-HJKMNP-TV-Z]{25}$
            examples:
              - whk_01JB3Y8Q2V7XN3M9R6T1Y5W8ZA
          example: whk_01JB3Y8Q2V7XN3M9R6T1Y5W8ZA
      responses:
        "200":
          description: The delivery result.
          headers:
            RateLimit:
              $ref: "#/components/headers/RateLimit"
            RateLimit-Policy:
              $ref: "#/components/headers/RateLimit-Policy"
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/WebhookTest"
              examples:
                illustrative:
                  summary: A delivered test
                  value:
                    event_id: evt_01JB4NB7T1Y5W8ZAK8Q2V7XN3M
                    delivered: true
                    status_code: 200
                    duration_ms: 182
        "400":
          $ref: "#/components/responses/BadRequest"
        "401":
          $ref: "#/components/responses/Unauthorized"
        "403":
          $ref: "#/components/responses/Forbidden"
        "404":
          $ref: "#/components/responses/NotFound"
        "429":
          $ref: "#/components/responses/RateLimited"
        default:
          $ref: "#/components/responses/ServerError"
      x-cost-credits: 0
      x-free: true
      x-anonymous: false
  /events:
    get:
      operationId: listEvents
      summary: List events
      description: Replay events from the last 30 days, oldest first, in the same envelope webhooks deliver. Use since to catch up after downtime.
      tags:
        - Webhooks
      security:
        - OAuth2:
            - webhooks:write
        - ApiKey:
            - webhooks:write
      parameters:
        - $ref: "#/components/parameters/MyDataHubVersion"
        - name: since
          in: query
          required: false
          description: Events after this time, up to 30 days back.
          schema:
            type: string
            format: date-time
          example: "2026-09-26T10:00:00Z"
        - name: type
          in: query
          required: false
          schema:
            type: string
            enum:
              - order.ready
              - order.failed
              - audience.refreshed
              - credits.low
              - credits.topped_up
              - warranty.replacement_issued
              - suppression.created
              - push.completed
              - push.partial
              - push.failed
              - connection.expired
        - name: limit
          in: query
          required: false
          description: Items per page, 1 to 100. Default 25.
          schema:
            default: 25
            type: integer
            minimum: 1
            maximum: 100
        - name: cursor
          in: query
          required: false
          description: An opaque cursor from next_cursor. Pass it back unchanged.
          schema:
            type: string
            minLength: 1
            maxLength: 512
      responses:
        "200":
          description: A page of events.
          headers:
            RateLimit:
              $ref: "#/components/headers/RateLimit"
            RateLimit-Policy:
              $ref: "#/components/headers/RateLimit-Policy"
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/EventList"
              examples:
                illustrative:
                  summary: Events since the order
                  value:
                    data:
                      - id: evt_01JB4NB7T1Y5W8ZAK8Q2V7XN3M
                        type: order.ready
                        timestamp: "2026-09-26T10:16:04Z"
                        api_version: "2027-01-15"
                        data:
                          order_id: ord_01JB4N9S3W2X6Y7Z8A1B2C3D4E
                          records: 5000
                          records_url: /v1/orders/ord_01JB4N9S3W2X6Y7Z8A1B2C3D4E/records
                          receipt_url: /v1/orders/ord_01JB4N9S3W2X6Y7Z8A1B2C3D4E/receipt
                    has_more: false
                    next_cursor: null
        "400":
          $ref: "#/components/responses/BadRequest"
        "401":
          $ref: "#/components/responses/Unauthorized"
        "403":
          $ref: "#/components/responses/Forbidden"
        "422":
          $ref: "#/components/responses/ValidationFailed"
        "429":
          $ref: "#/components/responses/RateLimited"
        default:
          $ref: "#/components/responses/ServerError"
      x-cost-credits: 0
      x-free: true
      x-anonymous: false
  /openapi.json:
    get:
      operationId: getOpenApiDocument
      summary: Get this OpenAPI document
      description: "This document: OpenAPI 3.1 with x-cost-credits and x-free on every operation, generated from the same schemas as the MCP tools."
      tags:
        - Meta
      security: []
      parameters:
        - $ref: "#/components/parameters/MyDataHubVersion"
      responses:
        "200":
          description: The OpenAPI document.
          headers:
            RateLimit:
              $ref: "#/components/headers/RateLimit"
            RateLimit-Policy:
              $ref: "#/components/headers/RateLimit-Policy"
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/OpenApiDocument"
              examples:
                illustrative:
                  summary: The first lines
                  value:
                    openapi: 3.1.1
                    info:
                      title: MyDataHub API
                      version: 2.0.0
        "400":
          $ref: "#/components/responses/BadRequest"
        "429":
          $ref: "#/components/responses/RateLimited"
        default:
          $ref: "#/components/responses/ServerError"
      x-cost-credits: 0
      x-free: true
      x-anonymous: true
  /health:
    get:
      operationId: getHealth
      summary: Check health
      description: Whether the API is up, with the release, the latest MyDataHub-Version, the current dataset version and how old its TPS and CTPS screen is.
      tags:
        - Meta
      security: []
      parameters:
        - $ref: "#/components/parameters/MyDataHubVersion"
      responses:
        "200":
          description: Health.
          headers:
            RateLimit:
              $ref: "#/components/headers/RateLimit"
            RateLimit-Policy:
              $ref: "#/components/headers/RateLimit-Policy"
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Health"
              examples:
                illustrative:
                  summary: Healthy
                  value:
                    status: ok
                    version: 2.0.0
                    api_version: "2027-01-15"
                    dataset_version: 2026-W39
                    tps_screen:
                      screened_at: "2026-09-25"
                      age_days: 1
                      status: ok
                    time: "2026-09-26T10:14:00Z"
                    status_page: https://status.emailmovers.com
        "400":
          $ref: "#/components/responses/BadRequest"
        "429":
          $ref: "#/components/responses/RateLimited"
        default:
          $ref: "#/components/responses/ServerError"
      x-cost-credits: 0
      x-free: true
      x-anonymous: true
webhooks:
  order.ready:
    post:
      operationId: webhookOrderReady
      summary: order.ready event
      description: "Delivered to your endpoint under Standard Webhooks: verify webhook-signature (HMAC-SHA256 over webhook-id, webhook-timestamp and the body with your whsec_ secret) and treat webhook-id as the idempotency key. Any 2xx acknowledges; anything else is retried over 24 hours with backoff."
      tags:
        - Webhooks
      security: []
      parameters:
        - name: webhook-id
          in: header
          required: true
          schema:
            type: string
          description: The event id; your idempotency key.
        - name: webhook-timestamp
          in: header
          required: true
          schema:
            type: string
          description: Unix seconds when the delivery was signed.
        - name: webhook-signature
          in: header
          required: true
          schema:
            type: string
          description: One or more space separated v1,base64 signatures.
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/OrderReadyEvent"
            examples:
              illustrative:
                summary: An order is ready
                value:
                  id: evt_01JB4NB7T1Y5W8ZAK8Q2V7XN3M
                  type: order.ready
                  timestamp: "2026-09-26T10:16:04Z"
                  api_version: "2027-01-15"
                  data:
                    order_id: ord_01JB4N9S3W2X6Y7Z8A1B2C3D4E
                    records: 5000
                    records_url: /v1/orders/ord_01JB4N9S3W2X6Y7Z8A1B2C3D4E/records
                    receipt_url: /v1/orders/ord_01JB4N9S3W2X6Y7Z8A1B2C3D4E/receipt
      responses:
        "200":
          description: Acknowledged.
        "400":
          description: Not acknowledged; the delivery is retried with backoff.
  order.failed:
    post:
      operationId: webhookOrderFailed
      summary: order.failed event
      description: "Delivered to your endpoint under Standard Webhooks: verify webhook-signature (HMAC-SHA256 over webhook-id, webhook-timestamp and the body with your whsec_ secret) and treat webhook-id as the idempotency key. Any 2xx acknowledges; anything else is retried over 24 hours with backoff."
      tags:
        - Webhooks
      security: []
      parameters:
        - name: webhook-id
          in: header
          required: true
          schema:
            type: string
          description: The event id; your idempotency key.
        - name: webhook-timestamp
          in: header
          required: true
          schema:
            type: string
          description: Unix seconds when the delivery was signed.
        - name: webhook-signature
          in: header
          required: true
          schema:
            type: string
          description: One or more space separated v1,base64 signatures.
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/OrderFailedEvent"
      responses:
        "200":
          description: Acknowledged.
        "400":
          description: Not acknowledged; the delivery is retried with backoff.
  audience.refreshed:
    post:
      operationId: webhookAudienceRefreshed
      summary: audience.refreshed event
      description: "Delivered to your endpoint under Standard Webhooks: verify webhook-signature (HMAC-SHA256 over webhook-id, webhook-timestamp and the body with your whsec_ secret) and treat webhook-id as the idempotency key. Any 2xx acknowledges; anything else is retried over 24 hours with backoff."
      tags:
        - Webhooks
      security: []
      parameters:
        - name: webhook-id
          in: header
          required: true
          schema:
            type: string
          description: The event id; your idempotency key.
        - name: webhook-timestamp
          in: header
          required: true
          schema:
            type: string
          description: Unix seconds when the delivery was signed.
        - name: webhook-signature
          in: header
          required: true
          schema:
            type: string
          description: One or more space separated v1,base64 signatures.
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/AudienceRefreshedEvent"
      responses:
        "200":
          description: Acknowledged.
        "400":
          description: Not acknowledged; the delivery is retried with backoff.
  credits.low:
    post:
      operationId: webhookCreditsLow
      summary: credits.low event
      description: "Delivered to your endpoint under Standard Webhooks: verify webhook-signature (HMAC-SHA256 over webhook-id, webhook-timestamp and the body with your whsec_ secret) and treat webhook-id as the idempotency key. Any 2xx acknowledges; anything else is retried over 24 hours with backoff."
      tags:
        - Webhooks
      security: []
      parameters:
        - name: webhook-id
          in: header
          required: true
          schema:
            type: string
          description: The event id; your idempotency key.
        - name: webhook-timestamp
          in: header
          required: true
          schema:
            type: string
          description: Unix seconds when the delivery was signed.
        - name: webhook-signature
          in: header
          required: true
          schema:
            type: string
          description: One or more space separated v1,base64 signatures.
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/CreditsLowEvent"
      responses:
        "200":
          description: Acknowledged.
        "400":
          description: Not acknowledged; the delivery is retried with backoff.
  credits.topped_up:
    post:
      operationId: webhookCreditsToppedUp
      summary: credits.topped_up event
      description: "Delivered to your endpoint under Standard Webhooks: verify webhook-signature (HMAC-SHA256 over webhook-id, webhook-timestamp and the body with your whsec_ secret) and treat webhook-id as the idempotency key. Any 2xx acknowledges; anything else is retried over 24 hours with backoff."
      tags:
        - Webhooks
      security: []
      parameters:
        - name: webhook-id
          in: header
          required: true
          schema:
            type: string
          description: The event id; your idempotency key.
        - name: webhook-timestamp
          in: header
          required: true
          schema:
            type: string
          description: Unix seconds when the delivery was signed.
        - name: webhook-signature
          in: header
          required: true
          schema:
            type: string
          description: One or more space separated v1,base64 signatures.
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/CreditsToppedUpEvent"
      responses:
        "200":
          description: Acknowledged.
        "400":
          description: Not acknowledged; the delivery is retried with backoff.
  warranty.replacement_issued:
    post:
      operationId: webhookWarrantyReplacementIssued
      summary: warranty.replacement_issued event
      description: "Delivered to your endpoint under Standard Webhooks: verify webhook-signature (HMAC-SHA256 over webhook-id, webhook-timestamp and the body with your whsec_ secret) and treat webhook-id as the idempotency key. Any 2xx acknowledges; anything else is retried over 24 hours with backoff."
      tags:
        - Webhooks
      security: []
      parameters:
        - name: webhook-id
          in: header
          required: true
          schema:
            type: string
          description: The event id; your idempotency key.
        - name: webhook-timestamp
          in: header
          required: true
          schema:
            type: string
          description: Unix seconds when the delivery was signed.
        - name: webhook-signature
          in: header
          required: true
          schema:
            type: string
          description: One or more space separated v1,base64 signatures.
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/WarrantyReplacementEvent"
      responses:
        "200":
          description: Acknowledged.
        "400":
          description: Not acknowledged; the delivery is retried with backoff.
  suppression.created:
    post:
      operationId: webhookSuppressionCreated
      summary: suppression.created event
      description: "Delivered to your endpoint under Standard Webhooks: verify webhook-signature (HMAC-SHA256 over webhook-id, webhook-timestamp and the body with your whsec_ secret) and treat webhook-id as the idempotency key. Any 2xx acknowledges; anything else is retried over 24 hours with backoff."
      tags:
        - Webhooks
      security: []
      parameters:
        - name: webhook-id
          in: header
          required: true
          schema:
            type: string
          description: The event id; your idempotency key.
        - name: webhook-timestamp
          in: header
          required: true
          schema:
            type: string
          description: Unix seconds when the delivery was signed.
        - name: webhook-signature
          in: header
          required: true
          schema:
            type: string
          description: One or more space separated v1,base64 signatures.
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/SuppressionCreatedEvent"
      responses:
        "200":
          description: Acknowledged.
        "400":
          description: Not acknowledged; the delivery is retried with backoff.
  push.completed:
    post:
      operationId: webhookPushCompleted
      summary: push.completed event
      description: "Delivered to your endpoint under Standard Webhooks: verify webhook-signature (HMAC-SHA256 over webhook-id, webhook-timestamp and the body with your whsec_ secret) and treat webhook-id as the idempotency key. Any 2xx acknowledges; anything else is retried over 24 hours with backoff."
      tags:
        - Webhooks
      security: []
      parameters:
        - name: webhook-id
          in: header
          required: true
          schema:
            type: string
          description: The event id; your idempotency key.
        - name: webhook-timestamp
          in: header
          required: true
          schema:
            type: string
          description: Unix seconds when the delivery was signed.
        - name: webhook-signature
          in: header
          required: true
          schema:
            type: string
          description: One or more space separated v1,base64 signatures.
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/PushCompletedEvent"
      responses:
        "200":
          description: Acknowledged.
        "400":
          description: Not acknowledged; the delivery is retried with backoff.
  push.partial:
    post:
      operationId: webhookPushPartial
      summary: push.partial event
      description: "Delivered to your endpoint under Standard Webhooks: verify webhook-signature (HMAC-SHA256 over webhook-id, webhook-timestamp and the body with your whsec_ secret) and treat webhook-id as the idempotency key. Any 2xx acknowledges; anything else is retried over 24 hours with backoff."
      tags:
        - Webhooks
      security: []
      parameters:
        - name: webhook-id
          in: header
          required: true
          schema:
            type: string
          description: The event id; your idempotency key.
        - name: webhook-timestamp
          in: header
          required: true
          schema:
            type: string
          description: Unix seconds when the delivery was signed.
        - name: webhook-signature
          in: header
          required: true
          schema:
            type: string
          description: One or more space separated v1,base64 signatures.
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/PushPartialEvent"
      responses:
        "200":
          description: Acknowledged.
        "400":
          description: Not acknowledged; the delivery is retried with backoff.
  push.failed:
    post:
      operationId: webhookPushFailed
      summary: push.failed event
      description: "Delivered to your endpoint under Standard Webhooks: verify webhook-signature (HMAC-SHA256 over webhook-id, webhook-timestamp and the body with your whsec_ secret) and treat webhook-id as the idempotency key. Any 2xx acknowledges; anything else is retried over 24 hours with backoff."
      tags:
        - Webhooks
      security: []
      parameters:
        - name: webhook-id
          in: header
          required: true
          schema:
            type: string
          description: The event id; your idempotency key.
        - name: webhook-timestamp
          in: header
          required: true
          schema:
            type: string
          description: Unix seconds when the delivery was signed.
        - name: webhook-signature
          in: header
          required: true
          schema:
            type: string
          description: One or more space separated v1,base64 signatures.
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/PushFailedEvent"
      responses:
        "200":
          description: Acknowledged.
        "400":
          description: Not acknowledged; the delivery is retried with backoff.
  connection.expired:
    post:
      operationId: webhookConnectionExpired
      summary: connection.expired event
      description: "Delivered to your endpoint under Standard Webhooks: verify webhook-signature (HMAC-SHA256 over webhook-id, webhook-timestamp and the body with your whsec_ secret) and treat webhook-id as the idempotency key. Any 2xx acknowledges; anything else is retried over 24 hours with backoff."
      tags:
        - Webhooks
      security: []
      parameters:
        - name: webhook-id
          in: header
          required: true
          schema:
            type: string
          description: The event id; your idempotency key.
        - name: webhook-timestamp
          in: header
          required: true
          schema:
            type: string
          description: Unix seconds when the delivery was signed.
        - name: webhook-signature
          in: header
          required: true
          schema:
            type: string
          description: One or more space separated v1,base64 signatures.
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/ConnectionExpiredEvent"
      responses:
        "200":
          description: Acknowledged.
        "400":
          description: Not acknowledged; the delivery is retried with backoff.
components:
  schemas:
    Account:
      type: object
      properties:
        organisation:
          type: object
          properties:
            id:
              type: string
              pattern: ^org_[0-7][0-9A-HJKMNP-TV-Z]{25}$
              description: "Format: an organisation id: org_ followed by a 26 character ULID in upper case Crockford base32, such as org_01JA0Q2V7XN3M9R6T1Y5W8ZAK8."
              examples:
                - org_01JA0Q2V7XN3M9R6T1Y5W8ZAK8
            name:
              type: string
            business_details:
              $ref: "#/components/schemas/BusinessDetails"
          required:
            - id
            - name
            - business_details
        user:
          anyOf:
            - type: object
              properties:
                id:
                  type: string
                  pattern: ^usr_[0-7][0-9A-HJKMNP-TV-Z]{25}$
                  description: "Format: a user id: usr_ followed by a 26 character ULID in upper case Crockford base32, such as usr_01JA0Q3V7XN3M9R6T1Y5W8ZAK8."
                  examples:
                    - usr_01JA0Q3V7XN3M9R6T1Y5W8ZAK8
                name:
                  type: string
              required:
                - id
                - name
            - type: "null"
          description: The signed-in person; null when the caller is an API key.
        mode:
          type: string
          enum:
            - live
            - test
          description: live keys (mdh_live_) spend real credits; test keys (mdh_test_) return synthetic data and never charge.
        plan:
          type: object
          properties:
            id:
              type: string
              pattern: ^[a-z0-9]+(_[a-z0-9]+)*$
            name:
              type: string
            kind:
              type: string
              enum:
                - free
                - subscription
                - pack
            renews_at:
              anyOf:
                - type: string
                  format: date
                - type: "null"
            cancel_at_period_end:
              type: boolean
          required:
            - id
            - name
            - kind
            - renews_at
            - cancel_at_period_end
        balance:
          type: object
          properties:
            credits:
              type: number
              description: Credits available to spend now.
            pack_credits:
              type: number
              description: "From credit packs: never expire."
            subscription_credits:
              type: number
              description: "From the plan: roll over while subscribed, up to the cap."
            rollover_cap:
              anyOf:
                - type: integer
                  minimum: 0
                - type: "null"
            low_threshold:
              anyOf:
                - type: integer
                  minimum: 0
                - type: "null"
              description: The credits.low webhook fires when the balance crosses this.
          required:
            - credits
            - pack_credits
            - subscription_credits
            - rollover_cap
            - low_threshold
          description: Credits by source.
        prices:
          type: array
          items:
            $ref: "#/components/schemas/CreditRule"
          description: What each action costs in credits.
        gbp_per_credit:
          type: number
          minimum: 0
          description: Your plan's price per credit, excluding VAT.
        rate_limit:
          $ref: "#/components/schemas/RateLimit"
        key:
          anyOf:
            - type: object
              properties:
                id:
                  type: string
                  pattern: ^key_[0-7][0-9A-HJKMNP-TV-Z]{25}$
                  description: "Format: an API key id: key_ followed by a 26 character ULID in upper case Crockford base32, such as key_01JB3Z4Q2V7XN3M9R6T1Y5W8ZA."
                  examples:
                    - key_01JB3Z4Q2V7XN3M9R6T1Y5W8ZA
                kind:
                  type: string
                  enum:
                    - standard
                    - agent
                budget:
                  anyOf:
                    - $ref: "#/components/schemas/Budget"
                    - type: "null"
                per_order_max_credits:
                  anyOf:
                    - type: integer
                      minimum: 0
                    - type: "null"
                expires_at:
                  anyOf:
                    - type: string
                      format: date-time
                    - type: "null"
              required:
                - id
                - kind
                - budget
                - per_order_max_credits
                - expires_at
            - type: "null"
          description: The calling key and, for an agent key, its budget; null for a person.
        account_manager:
          anyOf:
            - type: object
              properties:
                name:
                  type: string
                calendar_url:
                  type: string
                  format: uri
              required:
                - name
                - calendar_url
            - type: "null"
          description: The named person behind Talk to us.
        agreements:
          type: object
          properties:
            dlsa:
              $ref: "#/components/schemas/Agreement"
            dpa:
              $ref: "#/components/schemas/Agreement"
          required:
            - dlsa
            - dpa
          description: The Data Licence and Sharing Agreement (accepted with every order, or with an agent key that orders on its own) and the Article 28 DPA (accepted with the first suppression list).
      required:
        - organisation
        - user
        - mode
        - plan
        - balance
        - prices
        - gbp_per_credit
        - rate_limit
        - key
        - account_manager
        - agreements
      description: Balance, plan, per-action prices, rate limit and the calling key's budget.
    AccountAgreementsRequest:
      type: object
      properties:
        dpa_accepted_version:
          type: string
          minLength: 1
          maxLength: 100
          description: "The version of the Article 28 data processing agreement the owner accepts: the version in force, the account's agreements.dpa.version."
      required:
        - dpa_accepted_version
      additionalProperties: false
      description: An owner's acceptance of the Article 28 data processing agreement, which a suppression upload needs first.
    AccountCancellation:
      type: object
      properties:
        plan:
          type: string
          pattern: ^[a-z0-9]+(_[a-z0-9]+)*$
        cancel_at_period_end:
          type: boolean
          const: true
        ends_at:
          type: string
          format: date
        pack_credits_kept:
          type: number
          description: Credit pack credits never expire and survive cancellation.
        subscription_credits_usable_until:
          type: string
          format: date
        licences_unaffected:
          type: boolean
          const: true
          description: Delivered records stay licensed to the end of their term.
      required:
        - plan
        - cancel_at_period_end
        - ends_at
        - pack_credits_kept
        - subscription_credits_usable_until
        - licences_unaffected
      description: The plan ends at the end of its period; credits and licences survive to the end of their term.
    AccountCancellationRequest:
      type: object
      properties:
        reason:
          description: Optional; never required.
          type: string
          maxLength: 1000
      additionalProperties: false
      description: Cancel the rolling plan at period end.
    AccountUpdateRequest:
      type: object
      properties:
        business_name:
          type: string
          minLength: 1
          maxLength: 200
          pattern: ^\P{Cc}*$
          description: The organisation's business name, 1 to 200 characters, with no control character such as a line break.
        uk_business:
          type: boolean
          const: true
          description: true confirms that the organisation is a UK business.
      required:
        - business_name
        - uk_business
      additionalProperties: false
      description: The business name and a UK business confirmation, which a self-serve account needs before its first order. Only an organisation made by self-serve sign-up takes it.
    AccountUsage:
      type: object
      properties:
        from:
          type: string
          format: date
        to:
          type: string
          format: date
        credits_charged:
          type: number
          minimum: 0
          description: "Credits, to one decimal place: whole at 1 credit a record, fractional only where a rule is (0.1 company-only, 1.8 outright, both proposed)."
        credits_refunded:
          type: number
          minimum: 0
          description: "Credits, to one decimal place: whole at 1 credit a record, fractional only where a rule is (0.1 company-only, 1.8 outright, both proposed)."
        credits_added:
          type: number
          minimum: 0
          description: "Credits, to one decimal place: whole at 1 credit a record, fractional only where a rule is (0.1 company-only, 1.8 outright, both proposed)."
        by_action:
          type: array
          items:
            type: object
            properties:
              action:
                type: string
                pattern: ^[a-z0-9]+(_[a-z0-9]+)*$
              calls:
                type: integer
                minimum: 0
              credits:
                type: number
                minimum: 0
                description: "Credits, to one decimal place: whole at 1 credit a record, fractional only where a rule is (0.1 company-only, 1.8 outright, both proposed)."
            required:
              - action
              - calls
              - credits
        by_key:
          type: array
          items:
            type: object
            properties:
              key_id:
                type: string
                pattern: ^key_[0-7][0-9A-HJKMNP-TV-Z]{25}$
                description: "Format: an API key id: key_ followed by a 26 character ULID in upper case Crockford base32, such as key_01JB3Z4Q2V7XN3M9R6T1Y5W8ZA."
                examples:
                  - key_01JB3Z4Q2V7XN3M9R6T1Y5W8ZA
              label:
                type: string
              credits:
                type: number
                minimum: 0
                description: "Credits, to one decimal place: whole at 1 credit a record, fractional only where a rule is (0.1 company-only, 1.8 outright, both proposed)."
            required:
              - key_id
              - label
              - credits
      required:
        - from
        - to
        - credits_charged
        - credits_refunded
        - credits_added
        - by_action
        - by_key
      description: Credits charged, refunded and added in a period, by action and by key.
    ActionPathStep:
      type: object
      properties:
        step:
          type: integer
          minimum: 1
        action:
          type: string
          description: An MCP tool, a REST operation or a URL.
        rest:
          description: The REST operation, such as POST /v1/quotes.
          type:
            - string
            - "null"
        description:
          type: string
      required:
        - step
        - action
        - rest
        - description
      description: One step from a price to a delivered order.
    AgentBudgetProblem:
      type: object
      properties:
        type:
          type: string
          format: uri
          description: A URI under https://emailmovers.com/mydatahub/docs/errors/ naming the problem type.
        title:
          type: string
          description: A short summary of the problem type.
        status:
          type: integer
          minimum: 400
          maximum: 599
        detail:
          type: string
          description: What happened on this request, in plain English.
        instance:
          description: The request path or a request id this problem belongs to.
          type: string
        code:
          type: string
          const: agent-budget-exceeded
        next_step:
          type: string
          description: What to do next, in plain English.
        did_you_mean:
          description: The closest valid values, best first.
          type: array
          items:
            type: string
        errors:
          description: "For validation-failed: every problem found, each with a JSON pointer."
          type: array
          items:
            $ref: "#/components/schemas/ProblemError"
        required_scope:
          description: "For insufficient-scope: the scope to request, such as orders:write."
          type: string
        retry_after_seconds:
          description: "For rate-limited and service-unavailable: the same value as the Retry-After header."
          type: integer
          minimum: 0
        budget:
          $ref: "#/components/schemas/Budget"
        per_order_max_credits:
          type: integer
          minimum: 0
        quote_id:
          description: The machine quote the key could not settle; absent for an enrichment.
          type: string
          pattern: ^qte_[0-7][0-9A-HJKMNP-TV-Z]{25}$
          examples:
            - qte_01JB4M2R6T1Y5W8ZAK8Q2V7XN3
        credits_required:
          type: number
          minimum: 0
          description: "Credits, to one decimal place: whole at 1 credit a record, fractional only where a rule is (0.1 company-only, 1.8 outright, both proposed)."
        approval_url:
          type: string
          format: uri
          description: Where the key owner approves this spend in the app.
        top_up_url:
          type: string
          format: uri
      required:
        - type
        - title
        - status
        - detail
        - code
        - next_step
        - budget
        - approval_url
        - top_up_url
      description: "403: the agent key's per-order cap or monthly budget does not cover the machine quote or the enrichment. No order was created. The owner gets a signed notice only when an endpoint is registered at the key's webhook_url."
    AgentKey:
      type: object
      properties:
        id:
          type: string
          pattern: ^key_[0-7][0-9A-HJKMNP-TV-Z]{25}$
          description: "Format: an API key id: key_ followed by a 26 character ULID in upper case Crockford base32, such as key_01JB3Z4Q2V7XN3M9R6T1Y5W8ZA."
          examples:
            - key_01JB3Z4Q2V7XN3M9R6T1Y5W8ZA
        label:
          type: string
        mode:
          type: string
          enum:
            - live
            - test
          description: live keys (mdh_live_) spend real credits; test keys (mdh_test_) return synthetic data and never charge.
        prefix:
          type: string
          description: The first characters of the secret, such as mdh_live_7Kq2.
        scopes:
          type: array
          items:
            type: string
            enum:
              - account:read
              - counts:read
              - previews:read
              - audiences:read
              - audiences:write
              - quotes:read
              - orders:read
              - orders:write
              - records:read
              - enrich:write
              - suppressions:read
              - suppressions:write
              - pushes:write
              - webhooks:write
              - samples:request
              - admin
        ip_allowlist:
          type: array
          items:
            anyOf:
              - type: string
                pattern: ^((25[0-5]|2[0-4][0-9]|1[0-9][0-9]|[1-9][0-9]|[0-9])\.){3}(25[0-5]|2[0-4][0-9]|1[0-9][0-9]|[1-9][0-9]|[0-9])\/([0-9]|[1-2][0-9]|3[0-2])$
              - type: string
                pattern: ^(([0-9a-fA-F]{1,4}:){7}[0-9a-fA-F]{1,4}|([0-9a-fA-F]{1,4}:){1,7}:|([0-9a-fA-F]{1,4}:){1,6}:[0-9a-fA-F]{1,4}|([0-9a-fA-F]{1,4}:){1,5}(:[0-9a-fA-F]{1,4}){1,2}|([0-9a-fA-F]{1,4}:){1,4}(:[0-9a-fA-F]{1,4}){1,3}|([0-9a-fA-F]{1,4}:){1,3}(:[0-9a-fA-F]{1,4}){1,4}|([0-9a-fA-F]{1,4}:){1,2}(:[0-9a-fA-F]{1,4}){1,5}|[0-9a-fA-F]{1,4}:((:[0-9a-fA-F]{1,4}){1,6})|:((:[0-9a-fA-F]{1,4}){1,7}|:))\/(12[0-8]|1[01][0-9]|[1-9]?[0-9])$
            description: An IPv4 or IPv6 range in CIDR notation, such as 203.0.113.0/24.
          description: CIDR ranges the key may be used from; empty allows any address.
        created_at:
          type: string
          format: date-time
        created_by:
          type: string
          pattern: ^usr_[0-7][0-9A-HJKMNP-TV-Z]{25}$
          description: "Format: a user id: usr_ followed by a 26 character ULID in upper case Crockford base32, such as usr_01JA0Q3V7XN3M9R6T1Y5W8ZAK8."
          examples:
            - usr_01JA0Q3V7XN3M9R6T1Y5W8ZAK8
        last_used_at:
          anyOf:
            - type: string
              format: date-time
            - type: "null"
        expires_at:
          anyOf:
            - type: string
              format: date-time
            - type: "null"
        revoked_at:
          anyOf:
            - type: string
              format: date-time
            - type: "null"
        kind:
          type: string
          const: agent
        allowed_tools:
          type: array
          items:
            type: string
            enum:
              - get_plans
              - get_account
              - list_filters
              - search_filter_values
              - expand_job_titles
              - resolve_location
              - compile_audience
              - count_audience
              - preview_records
              - save_audience
              - list_audiences
              - list_field_sets
              - quote_order
              - create_order
              - get_order
              - fetch_order_records
              - push_to_crm
              - lookup_company
              - enrich_contact
              - check_suppression
              - report_objection
              - get_compliance_receipt
          description: The tools the key may call; a spending tool (create_order, enrich_contact) only if listed.
        allowed_connection_ids:
          type: array
          items:
            type: string
            pattern: ^conn_[0-7][0-9A-HJKMNP-TV-Z]{25}$
            description: "Format: a CRM connection id: conn_ followed by a 26 character ULID in upper case Crockford base32, such as conn_01JB3Y2X6Y7Z8A1B2C3D4E5F6G."
            examples:
              - conn_01JB3Y2X6Y7Z8A1B2C3D4E5F6G
          description: The CRM connections push_to_crm may push to live; empty allows dry runs only.
        monthly_budget_credits:
          type: integer
          minimum: 0
        per_order_max_credits:
          type: integer
          minimum: 0
        budget:
          description: The live spend meter.
          $ref: "#/components/schemas/Budget"
        webhook_url:
          anyOf:
            - type: string
              format: uri
            - type: "null"
        disclosure:
          type: string
          enum:
            - account
            - anonymous
          description: "account: counts as the organisation's own; anonymous: every anonymous visitor's rule, for a public assistant."
      required:
        - id
        - label
        - mode
        - prefix
        - scopes
        - ip_allowlist
        - created_at
        - created_by
        - last_used_at
        - expires_at
        - revoked_at
        - kind
        - allowed_tools
        - allowed_connection_ids
        - monthly_budget_credits
        - per_order_max_credits
        - budget
        - webhook_url
        - disclosure
      description: "An API key with kind agent: it spends without a human step only when the tool is in allowed_tools, per_order_max_credits covers the machine quote and monthly budget remains. Only a person in the app changes these limits, never a key."
    Agreement:
      type: object
      properties:
        name:
          type: string
        version:
          type: string
          description: "The version in force: draft until the text is approved."
        url:
          type: string
          format: uri
        accepted:
          anyOf:
            - type: object
              properties:
                version:
                  type: string
                accepted_at:
                  type: string
                  format: date-time
                accepted_by:
                  type: string
                  pattern: ^usr_[0-7][0-9A-HJKMNP-TV-Z]{25}$
                  description: "Format: a user id: usr_ followed by a 26 character ULID in upper case Crockford base32, such as usr_01JA0Q3V7XN3M9R6T1Y5W8ZAK8."
                  examples:
                    - usr_01JA0Q3V7XN3M9R6T1Y5W8ZAK8
              required:
                - version
                - accepted_at
                - accepted_by
            - type: "null"
          description: "The organisation's acceptance of the latest version it accepted: which version, when and by whom, first time for that version. null until one is accepted. When version (the one in force) differs, the agreement is accepted again."
      required:
        - name
        - version
        - url
        - accepted
    AllGroup:
      type: object
      properties:
        all:
          minItems: 1
          type: array
          items:
            $ref: "#/components/schemas/WhereNode"
      required:
        - all
      additionalProperties: false
      description: "AND: every node inside must match."
    AnyGroup:
      type: object
      properties:
        any:
          minItems: 1
          type: array
          items:
            $ref: "#/components/schemas/WhereNode"
      required:
        - any
      additionalProperties: false
      description: "OR: at least one node inside must match."
    ApiKey:
      oneOf:
        - $ref: "#/components/schemas/StandardApiKey"
        - $ref: "#/components/schemas/AgentKey"
      description: A standard API key or an agent key. The secret is never shown again.
      discriminator:
        propertyName: kind
        mapping:
          standard: "#/components/schemas/StandardApiKey"
          agent: "#/components/schemas/AgentKey"
    ApiKeyCreated:
      type: object
      properties:
        key:
          $ref: "#/components/schemas/ApiKey"
        secret:
          type: string
          pattern: ^mdh_(live|test)_[0-9A-Za-z]{32}$
          description: "The secret, shown once. Send it as Authorization: Bearer."
      required:
        - key
        - secret
      description: A new key and its secret, shown once.
    ApiKeyList:
      type: object
      properties:
        data:
          type: array
          items:
            $ref: "#/components/schemas/ApiKey"
        has_more:
          type: boolean
          description: Whether another page follows.
        next_cursor:
          anyOf:
            - type: string
              minLength: 1
              maxLength: 512
              description: An opaque cursor from next_cursor. Pass it back unchanged.
            - type: "null"
          description: Pass as cursor to fetch the next page; null on the last page.
      required:
        - data
        - has_more
        - next_cursor
      description: A page of API keys, newest first.
    ApiKeyRequest:
      type: object
      properties:
        kind:
          type: string
          enum:
            - standard
            - agent
        label:
          type: string
          minLength: 1
          maxLength: 100
        mode:
          default: live
          type: string
          enum:
            - live
            - test
          description: live keys (mdh_live_) spend real credits; test keys (mdh_test_) return synthetic data and never charge.
        scopes:
          minItems: 1
          type: array
          items:
            type: string
            enum:
              - account:read
              - counts:read
              - previews:read
              - audiences:read
              - audiences:write
              - quotes:read
              - orders:read
              - orders:write
              - records:read
              - enrich:write
              - suppressions:read
              - suppressions:write
              - pushes:write
              - webhooks:write
              - samples:request
              - admin
          description: The key's scopes. An agent key never takes admin, and no key takes account:write, which only a signed-in owner uses.
        ip_allowlist:
          type: array
          items:
            anyOf:
              - type: string
                pattern: ^((25[0-5]|2[0-4][0-9]|1[0-9][0-9]|[1-9][0-9]|[0-9])\.){3}(25[0-5]|2[0-4][0-9]|1[0-9][0-9]|[1-9][0-9]|[0-9])\/([0-9]|[1-2][0-9]|3[0-2])$
              - type: string
                pattern: ^(([0-9a-fA-F]{1,4}:){7}[0-9a-fA-F]{1,4}|([0-9a-fA-F]{1,4}:){1,7}:|([0-9a-fA-F]{1,4}:){1,6}:[0-9a-fA-F]{1,4}|([0-9a-fA-F]{1,4}:){1,5}(:[0-9a-fA-F]{1,4}){1,2}|([0-9a-fA-F]{1,4}:){1,4}(:[0-9a-fA-F]{1,4}){1,3}|([0-9a-fA-F]{1,4}:){1,3}(:[0-9a-fA-F]{1,4}){1,4}|([0-9a-fA-F]{1,4}:){1,2}(:[0-9a-fA-F]{1,4}){1,5}|[0-9a-fA-F]{1,4}:((:[0-9a-fA-F]{1,4}){1,6})|:((:[0-9a-fA-F]{1,4}){1,7}|:))\/(12[0-8]|1[01][0-9]|[1-9]?[0-9])$
            description: An IPv4 or IPv6 range in CIDR notation, such as 203.0.113.0/24.
          description: CIDR ranges the key may be used from; empty allows any address.
        expires_at:
          type: string
          format: date-time
          description: RFC 3339 date and time, with Z or an offset such as +01:00.
        allowed_tools:
          description: "Agent keys only, and required there: the tools the key may call."
          type: array
          items:
            type: string
            enum:
              - get_plans
              - get_account
              - list_filters
              - search_filter_values
              - expand_job_titles
              - resolve_location
              - compile_audience
              - count_audience
              - preview_records
              - save_audience
              - list_audiences
              - list_field_sets
              - quote_order
              - create_order
              - get_order
              - fetch_order_records
              - push_to_crm
              - lookup_company
              - enrich_contact
              - check_suppression
              - report_objection
              - get_compliance_receipt
        allowed_connection_ids:
          description: "Agent keys only: the connections push_to_crm may push to live. Default none."
          type: array
          items:
            type: string
            pattern: ^conn_[0-7][0-9A-HJKMNP-TV-Z]{25}$
            description: "Format: a CRM connection id: conn_ followed by a 26 character ULID in upper case Crockford base32, such as conn_01JB3Y2X6Y7Z8A1B2C3D4E5F6G."
            examples:
              - conn_01JB3Y2X6Y7Z8A1B2C3D4E5F6G
        monthly_budget_credits:
          description: Agent keys only. Default 1000 credits a month (to confirm).
          type: integer
          minimum: 0
        per_order_max_credits:
          description: "Agent keys only, and required there: the most one order may spend, no more than monthly_budget_credits."
          type: integer
          minimum: 0
        webhook_url:
          description: "Agent keys only: the https endpoint its events go to."
          type: string
          format: uri
          pattern: ^https://
        disclosure:
          description: Agent keys only. anonymous makes every count follow an anonymous visitor's rules, for a public assistant. Default account.
          type: string
          enum:
            - account
            - anonymous
        licence_accepted_version:
          description: "Agent keys only, and required for one that spends on its own (create_order or enrich_contact, with a monthly budget and a per-order cap above 0): the version of the Data Licence and Sharing Agreement the person making the key accepts, the account's agreements.dlsa.version. Every order and enrichment the key makes on its own carries that acceptance. A key with a budget or cap of 0 needs none: a person approves, and accepts, each of its spends."
          type: string
          minLength: 1
          maxLength: 100
      required:
        - kind
        - label
        - scopes
      additionalProperties: false
      description: A new key, made by a signed-in person (an OAuth user token with admin), never by a key. An agent key needs allowed_tools and per_order_max_credits, no more than its monthly budget, and never takes admin.
      allOf:
        - if:
            type: object
            properties:
              kind:
                const: agent
            required:
              - kind
          then:
            type: object
            properties:
              allowed_tools: true
              per_order_max_credits: true
              scopes:
                type: array
                not:
                  contains:
                    const: admin
            required:
              - allowed_tools
              - per_order_max_credits
          else:
            type: object
            properties:
              allowed_tools: false
              allowed_connection_ids: false
              monthly_budget_credits: false
              per_order_max_credits: false
              webhook_url: false
              disclosure: false
    ApiKeyUpdate:
      type: object
      properties:
        label:
          type: string
          minLength: 1
          maxLength: 100
        scopes:
          description: Replacement scopes. An agent key never takes admin, and no key takes account:write, which only a signed-in owner uses.
          minItems: 1
          type: array
          items:
            type: string
            enum:
              - account:read
              - counts:read
              - previews:read
              - audiences:read
              - audiences:write
              - quotes:read
              - orders:read
              - orders:write
              - records:read
              - enrich:write
              - suppressions:read
              - suppressions:write
              - pushes:write
              - webhooks:write
              - samples:request
              - admin
        ip_allowlist:
          type: array
          items:
            anyOf:
              - type: string
                pattern: ^((25[0-5]|2[0-4][0-9]|1[0-9][0-9]|[1-9][0-9]|[0-9])\.){3}(25[0-5]|2[0-4][0-9]|1[0-9][0-9]|[1-9][0-9]|[0-9])\/([0-9]|[1-2][0-9]|3[0-2])$
              - type: string
                pattern: ^(([0-9a-fA-F]{1,4}:){7}[0-9a-fA-F]{1,4}|([0-9a-fA-F]{1,4}:){1,7}:|([0-9a-fA-F]{1,4}:){1,6}:[0-9a-fA-F]{1,4}|([0-9a-fA-F]{1,4}:){1,5}(:[0-9a-fA-F]{1,4}){1,2}|([0-9a-fA-F]{1,4}:){1,4}(:[0-9a-fA-F]{1,4}){1,3}|([0-9a-fA-F]{1,4}:){1,3}(:[0-9a-fA-F]{1,4}){1,4}|([0-9a-fA-F]{1,4}:){1,2}(:[0-9a-fA-F]{1,4}){1,5}|[0-9a-fA-F]{1,4}:((:[0-9a-fA-F]{1,4}){1,6})|:((:[0-9a-fA-F]{1,4}){1,7}|:))\/(12[0-8]|1[01][0-9]|[1-9]?[0-9])$
            description: An IPv4 or IPv6 range in CIDR notation, such as 203.0.113.0/24.
          description: CIDR ranges the key may be used from; empty allows any address.
        expires_at:
          anyOf:
            - type: string
              format: date-time
              description: RFC 3339 date and time, with Z or an offset such as +01:00.
            - type: "null"
        allowed_tools:
          type: array
          items:
            type: string
            enum:
              - get_plans
              - get_account
              - list_filters
              - search_filter_values
              - expand_job_titles
              - resolve_location
              - compile_audience
              - count_audience
              - preview_records
              - save_audience
              - list_audiences
              - list_field_sets
              - quote_order
              - create_order
              - get_order
              - fetch_order_records
              - push_to_crm
              - lookup_company
              - enrich_contact
              - check_suppression
              - report_objection
              - get_compliance_receipt
        allowed_connection_ids:
          type: array
          items:
            type: string
            pattern: ^conn_[0-7][0-9A-HJKMNP-TV-Z]{25}$
            description: "Format: a CRM connection id: conn_ followed by a 26 character ULID in upper case Crockford base32, such as conn_01JB3Y2X6Y7Z8A1B2C3D4E5F6G."
            examples:
              - conn_01JB3Y2X6Y7Z8A1B2C3D4E5F6G
        monthly_budget_credits:
          type: integer
          minimum: 0
        per_order_max_credits:
          type: integer
          minimum: 0
        webhook_url:
          anyOf:
            - type: string
              format: uri
              pattern: ^https://
            - type: "null"
        disclosure:
          type: string
          enum:
            - account
            - anonymous
        licence_accepted_version:
          description: "The Data Licence and Sharing Agreement version the person changing the key accepts: required when the change lets an agent key spend on its own (create_order or enrich_contact, a budget and a cap above 0), or widens what it spends (a spending tool added, a larger budget or cap), and the key has not accepted the version in force. Sent alone, it renews the key's acceptance."
          type: string
          minLength: 1
          maxLength: 100
      additionalProperties: false
      description: Members to change on a key, at least one. Only a signed-in person changes a key; a key never changes its own tools, budget or cap.
      minProperties: 1
    Audience:
      type: object
      properties:
        schema:
          type: string
          const: mydatahub.audience/1
          description: The definition's namespace and version. Older versions are upgraded by a pure function.
        dataset:
          type: string
          enum:
            - uk_contacts
            - ie_contacts
            - global_contacts
          description: The dataset an audience draws from. uk_contacts is live; ie_contacts and global_contacts are listed but not yet served.
        dataset_version:
          description: The weekly build to count against, such as 2026-W39. Left out, the current version is filled in and returned.
          type: string
          pattern: ^[0-9]{4}-W(0[1-9]|[1-4][0-9]|5[0-3])$
          examples:
            - 2026-W39
        where:
          description: The filters. Left out, the audience is the whole dataset after the defaults.
          $ref: "#/components/schemas/WhereNode"
        email_policy:
          $ref: "#/components/schemas/EmailPolicy"
        dedupe:
          type: string
          enum:
            - none
            - one_per_company
            - one_per_site
          description: none keeps every matching contact, one_per_company keeps one contact per company, one_per_site keeps one per site. Default none.
      required:
        - schema
      additionalProperties: false
      title: MyDataHub audience definition (mydatahub.audience/1)
      description: Who you want, as filters grouped by entity (company., site., contact., email., phone., signal.). The server fills its defaults (active companies, named emails, corporate subscribers, the current dataset_version) and returns the effective definition with every count.
    AudienceCountSummary:
      type: object
      properties:
        count_id:
          type: string
          pattern: ^cnt_[A-Z2-7]{20}$
          description: "Format: a count id: cnt_ followed by 20 base32 characters (A to Z and 2 to 7), such as cnt_7K2PQ4MAXN3R6TBY5WDZ."
          examples:
            - cnt_7K2PQ4MAXN3R6TBY5WDZ
        dataset_version:
          type: string
          pattern: ^[0-9]{4}-W(0[1-9]|[1-4][0-9]|5[0-3])$
          description: The immutable weekly build a count is bound to, such as 2026-W39. The last 52 stay queryable.
          examples:
            - 2026-W39
        contacts:
          type: integer
          minimum: 0
        companies:
          type: integer
          minimum: 0
        emailable:
          type: integer
          minimum: 0
        counted_at:
          type: string
          format: date-time
      required:
        - count_id
        - dataset_version
        - contacts
        - companies
        - emailable
        - counted_at
      description: The headline numbers of one count of an audience.
    AudienceRef:
      anyOf:
        - type: string
          pattern: ^aud_[0-7][0-9A-HJKMNP-TV-Z]{25}$
          description: "Format: an audience id: aud_ followed by a 26 character ULID in upper case Crockford base32, such as aud_01JB4H2K9V6XN3M9R6T1Y5W8ZA."
          examples:
            - aud_01JB4H2K9V6XN3M9R6T1Y5W8ZA
        - $ref: "#/components/schemas/Audience"
      description: An audience definition (mydatahub.audience/1) inline, or the aud_ id of a saved audience.
    AudienceRefresh:
      type: object
      properties:
        audience_id:
          type: string
          pattern: ^aud_[0-7][0-9A-HJKMNP-TV-Z]{25}$
          description: "Format: an audience id: aud_ followed by a 26 character ULID in upper case Crockford base32, such as aud_01JB4H2K9V6XN3M9R6T1Y5W8ZA."
          examples:
            - aud_01JB4H2K9V6XN3M9R6T1Y5W8ZA
        previous:
          anyOf:
            - $ref: "#/components/schemas/AudienceCountSummary"
            - type: "null"
        current:
          $ref: "#/components/schemas/AudienceCountSummary"
        diff:
          type: object
          properties:
            contacts:
              type: integer
            companies:
              type: integer
            emailable:
              type: integer
            new_records:
              type: integer
              minimum: 0
              description: Records in the audience that were not before.
            removed_records:
              type: integer
              minimum: 0
          required:
            - contacts
            - companies
            - emailable
            - new_records
            - removed_records
      required:
        - audience_id
        - previous
        - current
        - diff
      description: A saved audience recounted on the current dataset version, with the difference.
    AudienceRefreshedEvent:
      type: object
      properties:
        id:
          type: string
          pattern: ^evt_[0-7][0-9A-HJKMNP-TV-Z]{25}$
          description: Also sent as the webhook-id header; your idempotency key.
          examples:
            - evt_01JB4NB7T1Y5W8ZAK8Q2V7XN3M
        type:
          type: string
          const: audience.refreshed
        timestamp:
          type: string
          format: date-time
        api_version:
          type: string
          pattern: ^[0-9]{4}-(0[1-9]|1[0-2])-(0[1-9]|[12][0-9]|3[01])$
          description: A dated MyDataHub-Version, such as 2027-01-15.
        data:
          $ref: "#/components/schemas/AudienceRefresh"
      required:
        - id
        - type
        - timestamp
        - api_version
        - data
      description: A saved audience was recounted, with the diff.
    BetweenFilter:
      type: object
      properties:
        field:
          type: string
          enum:
            - company.employee_band
            - company.turnover_band
            - company.fleet_band
            - contact.job_level_rank
            - email.deliverability_score
        op:
          type: string
          const: between
        from:
          anyOf:
            - type: string
              minLength: 1
              maxLength: 200
            - type: number
          description: A band code or a number from the field's order.
        to:
          anyOf:
            - type: string
              minLength: 1
              maxLength: 200
            - type: number
          description: A band code or a number from the field's order.
      required:
        - field
        - op
        - from
        - to
      additionalProperties: false
      description: The field lies from one value to another inclusive, over the field's order.
      examples:
        - field: company.employee_band
          op: between
          from: 10-19
          to: 200-349
      allOf:
        - if:
            type: object
            properties:
              field:
                const: company.employee_band
            required:
              - field
          then:
            type: object
            properties:
              from:
                type: string
                enum:
                  - 1-5
                  - 6-9
                  - 10-19
                  - 20-49
                  - 50-99
                  - 100-199
                  - 200-349
                  - 350-499
                  - 500-999
                  - 1000-1499
                  - 1500-4999
                  - 5000-9999
                  - 10000+
              to:
                type: string
                enum:
                  - 1-5
                  - 6-9
                  - 10-19
                  - 20-49
                  - 50-99
                  - 100-199
                  - 200-349
                  - 350-499
                  - 500-999
                  - 1000-1499
                  - 1500-4999
                  - 5000-9999
                  - 10000+
        - if:
            type: object
            properties:
              field:
                const: company.turnover_band
            required:
              - field
          then:
            type: object
            properties:
              from:
                type: string
                enum:
                  - 1-100k
                  - 101k-200k
                  - 201k-500k
                  - 501k-750k
                  - 751k-1M
                  - 1.1M-2M
                  - 2.1M-5M
                  - 5.1M-7.5M
                  - 7.6M-10M
                  - 11M-20M
                  - 21M-50M
                  - 51M-75M
                  - 76M-100M
                  - 100M-250M
                  - 251M-500M
                  - 501M-1B
                  - 1B+
              to:
                type: string
                enum:
                  - 1-100k
                  - 101k-200k
                  - 201k-500k
                  - 501k-750k
                  - 751k-1M
                  - 1.1M-2M
                  - 2.1M-5M
                  - 5.1M-7.5M
                  - 7.6M-10M
                  - 11M-20M
                  - 21M-50M
                  - 51M-75M
                  - 76M-100M
                  - 100M-250M
                  - 251M-500M
                  - 501M-1B
                  - 1B+
        - if:
            type: object
            properties:
              field:
                const: company.fleet_band
            required:
              - field
          then:
            type: object
            properties:
              from:
                type: string
                enum:
                  - 1-2
                  - 3-5
                  - 6-9
                  - 10-19
                  - 20-49
                  - 50-99
                  - 100+
              to:
                type: string
                enum:
                  - 1-2
                  - 3-5
                  - 6-9
                  - 10-19
                  - 20-49
                  - 50-99
                  - 100+
        - if:
            type: object
            properties:
              field:
                const: contact.job_level_rank
            required:
              - field
          then:
            type: object
            properties:
              from:
                type: integer
                enum:
                  - 1
                  - 2
                  - 3
                  - 4
                  - 5
                  - 6
                  - 7
                  - 8
                  - 9
                  - 10
                  - 11
                  - 12
                  - 13
              to:
                type: integer
                enum:
                  - 1
                  - 2
                  - 3
                  - 4
                  - 5
                  - 6
                  - 7
                  - 8
                  - 9
                  - 10
                  - 11
                  - 12
                  - 13
        - if:
            type: object
            properties:
              field:
                const: email.deliverability_score
            required:
              - field
          then:
            type: object
            properties:
              from:
                type: integer
                minimum: 0
                maximum: 100
              to:
                type: integer
                minimum: 0
                maximum: 100
    BillingAddress:
      type: object
      properties:
        line1:
          type:
            - string
            - "null"
        line2:
          type:
            - string
            - "null"
        city:
          type:
            - string
            - "null"
        postal_code:
          type:
            - string
            - "null"
        state:
          description: A county or region, when given.
          type:
            - string
            - "null"
        country:
          description: Two letters, such as GB.
          type:
            - string
            - "null"
      required:
        - line1
        - line2
        - city
        - postal_code
        - state
        - country
      description: The billing address Stripe Checkout collected.
    Breakdown:
      type: object
      properties:
        by:
          type: string
          enum:
            - company.sic_2007
            - company.business_class
            - company.employee_band
            - company.turnover_band
            - company.fleet_band
            - company.type
            - company.status
            - company.subscriber_class
            - site.region
            - site.county
            - site.town
            - site.postcode
            - contact.title_family
            - contact.job_function
            - contact.job_level_rank
            - email.exists
            - email.type
            - phone.exists
            - phone.tps_ctps_status
          description: A field a count can be broken down by.
        cells:
          maxItems: 50
          type: array
          items:
            $ref: "#/components/schemas/BreakdownCell"
        other:
          anyOf:
            - $ref: "#/components/schemas/OtherBucket"
            - type: "null"
      required:
        - by
        - cells
      description: 'A count split by one field: the top 50 cells plus other. Cells under 10 read "under 10".'
    BreakdownCell:
      type: object
      properties:
        value:
          description: The cell's code as shown, such as director or C. To add the cell to the audience as a filter, use filter_value when present, otherwise value.
          anyOf:
            - type: string
            - type: number
            - type: boolean
        filter_value:
          description: The value a filter takes when it differs from value, such as 6 for director on contact.job_level_rank (filters take ranks).
          anyOf:
            - type: string
            - type: number
            - type: boolean
        label:
          type: string
        contacts:
          $ref: "#/components/schemas/CellCount"
        companies:
          $ref: "#/components/schemas/CellCount"
      required:
        - value
        - contacts
      description: One cell of a breakdown.
    BriefQuestion:
      type: object
      properties:
        id:
          type: string
        text:
          type: string
        options:
          type: array
          items:
            type: object
            properties:
              label:
                type: string
              filter:
                $ref: "#/components/schemas/Filter"
            required:
              - label
              - filter
      required:
        - id
        - text
        - options
      description: An ambiguity to answer before counting.
    BriefRequest:
      type: object
      properties:
        brief:
          type: string
          minLength: 3
          maxLength: 2000
          description: A plain English brief, such as "300 finance directors at law firms in Manchester".
        dataset:
          type: string
          enum:
            - uk_contacts
            - ie_contacts
            - global_contacts
          description: The dataset an audience draws from. uk_contacts is live; ie_contacts and global_contacts are listed but not yet served.
      required:
        - brief
      additionalProperties: false
      description: A brief to compile into an audience definition.
    Budget:
      type: object
      properties:
        monthly_credits:
          type: integer
          minimum: 0
        spent:
          type: number
          minimum: 0
          description: "Credits, to one decimal place: whole at 1 credit a record, fractional only where a rule is (0.1 company-only, 1.8 outright, both proposed)."
        remaining:
          type: number
          minimum: 0
          description: "Credits, to one decimal place: whole at 1 credit a record, fractional only where a rule is (0.1 company-only, 1.8 outright, both proposed)."
        resets_at:
          type: string
          format: date-time
      required:
        - monthly_credits
        - spent
        - remaining
        - resets_at
      description: An agent key's monthly budget in credits.
    BundleNode:
      type: object
      properties:
        bundle:
          type: string
          enum:
            - sdm
            - manager_and_above
            - owner_managed
      required:
        - bundle
      additionalProperties: false
      description: "A title bundle, resolved to its filters in the effective definition: sdm (senior decision makers), manager_and_above (managers and above), owner_managed (owner managed)."
    BusinessDetails:
      type: object
      properties:
        confirmed_at:
          anyOf:
            - type: string
              format: date-time
            - type: "null"
          description: When a person confirmed the business name and that it is a UK business; null until then.
        required_before_order:
          type: boolean
          description: "true: an order or an enrichment is refused with business-details-required until a person who owns the account confirms them with PATCH /v1/account. Only an organisation made by self-serve sign-up needs them, and whether it does is set when it is made."
        name_editable:
          type: boolean
          description: true for an organisation made by self-serve sign-up, whose business name PATCH /v1/account sets. false for one Emailmovers set up, which keeps the name staff gave it and needs no business details before an order.
      required:
        - confirmed_at
        - required_before_order
        - name_editable
      description: Whether the business name and a UK business confirmation are on record, which a self-serve account needs before its first order.
    BuyerKit:
      type: object
      properties:
        draft_lia_url:
          type: string
          format: uri
        first_email_wording_url:
          type: string
          format: uri
        article30_stub_url:
          type: string
          format: uri
        due_diligence_file_url:
          type: string
          format: uri
      required:
        - draft_lia_url
        - first_email_wording_url
        - article30_stub_url
        - due_diligence_file_url
      description: Four signed documents that help the buyer use the list lawfully.
    CellCount:
      anyOf:
        - type: integer
          minimum: 0
        - type: string
          const: under 10
        - type: string
          const: suppressed
      description: A whole number; "under 10" when disclosure control withholds a small cell; or "suppressed" when secondary suppression hides a cell of 10 or more in a cross-tab or breakdown, so totals cannot be differenced. Anonymous and trial callers see cells rounded to the nearest 5.
      examples:
        - 4120
        - under 10
        - suppressed
    Chip:
      type: object
      properties:
        label:
          type: string
          description: What the chip reads, such as Within 20 miles of Leeds.
        pointer:
          type: string
          description: JSON pointer to the filter inside audience.
        from_brief:
          type: string
          description: The words of the brief it came from.
      required:
        - label
        - pointer
        - from_brief
      description: One visible, editable filter compiled from the brief.
    CompanyCompliance:
      type: object
      properties:
        lawful_basis:
          type: string
          enum:
            - legitimate_interests_art6_1_f
        subscriber_class:
          type: string
          enum:
            - ltd
            - plc
            - llp
            - scottish_partnership
            - public_body
            - charity_incorporated
            - sole_trader
            - ordinary_partnership
            - unknown
        corporate_subscriber:
          type: boolean
        email_marketing_basis:
          type: string
          enum:
            - pecr_corporate_subscriber
            - consent_required
        subscriber_evidence:
          type: object
          properties:
            companies_house_number:
              type:
                - string
                - "null"
            company_type:
              type: string
          required:
            - companies_house_number
            - company_type
        provenance:
          type: object
          properties:
            company:
              type: object
              properties:
                source:
                  type: string
                  enum:
                    - emailmovers_research
                    - companies_house
                    - onspd
                    - ukbizdirectory
                    - partner_rampedup
                as_of:
                  type: string
                  format: date
              required:
                - source
                - as_of
          required:
            - company
        attribution:
          type:
            - string
            - "null"
      required:
        - lawful_basis
        - subscriber_class
        - corporate_subscriber
        - email_marketing_basis
        - subscriber_evidence
        - provenance
        - attribution
      description: The company facts of the compliance object, for results with no contact.
    CompanyLookup:
      type: object
      properties:
        match_confidence:
          type: number
          minimum: 0
          maximum: 1
        dataset_version:
          type: string
          pattern: ^[0-9]{4}-W(0[1-9]|[1-4][0-9]|5[0-3])$
          description: The immutable weekly build a count is bound to, such as 2026-W39. The last 52 stay queryable.
          examples:
            - 2026-W39
        company:
          type: object
          properties:
            companies_house_number:
              type:
                - string
                - "null"
            name:
              type: string
            status:
              type: string
            type:
              type: string
            subscriber_class:
              type: string
              enum:
                - ltd
                - plc
                - llp
                - scottish_partnership
                - public_body
                - charity_incorporated
                - sole_trader
                - ordinary_partnership
                - unknown
            incorporation_date:
              anyOf:
                - type: string
                  format: date
                - type: "null"
            sic_2007:
              type: array
              items:
                $ref: "#/components/schemas/SicCode"
            employee_band:
              type:
                - string
                - "null"
            turnover_band:
              type:
                - string
                - "null"
            website_domain:
              type:
                - string
                - "null"
            registered_office:
              type: object
              properties:
                town:
                  type:
                    - string
                    - "null"
                postcode:
                  type:
                    - string
                    - "null"
              required:
                - town
                - postcode
          required:
            - companies_house_number
            - name
            - status
            - type
            - subscriber_class
            - incorporation_date
            - sic_2007
            - employee_band
            - turnover_band
            - website_domain
            - registered_office
        contacts_held:
          anyOf:
            - $ref: "#/components/schemas/CellCount"
            - type: "null"
          description: "Contacts we hold at the company: a single-company count, so signed-in callers only (null otherwise) and logged. Trial callers see it rounded."
        contacts_by_level:
          anyOf:
            - type: array
              items:
                $ref: "#/components/schemas/LevelCount"
            - type: "null"
          description: The same count by job level, most senior first, with Level not known last; null when signed out.
        disclosure:
          $ref: "#/components/schemas/Disclosure"
        compliance:
          $ref: "#/components/schemas/CompanyCompliance"
        credits_charged:
          type: number
          const: 0
      required:
        - match_confidence
        - dataset_version
        - company
        - contacts_held
        - contacts_by_level
        - disclosure
        - compliance
        - credits_charged
      description: Companies House fields for one company and, for signed-in callers, the contacts held there. No contact details.
    CompanyLookupRequest:
      type: object
      properties:
        company_number:
          type: string
          pattern: ^[A-Z0-9]{8}$
          description: Eight characters in upper case, such as 05046417 or SC123456.
        domain:
          type: string
          minLength: 3
          maxLength: 253
        name:
          type: string
          minLength: 2
          maxLength: 200
        town:
          description: Narrows a name search.
          type: string
          maxLength: 100
      additionalProperties: false
      description: A company by exactly one of number, domain or name.
      oneOf:
        - type: object
          properties:
            company_number: true
          required:
            - company_number
        - type: object
          properties:
            domain: true
          required:
            - domain
        - type: object
          properties:
            name: true
          required:
            - name
    CompiledBrief:
      type: object
      properties:
        audience:
          $ref: "#/components/schemas/Audience"
        readback:
          type: string
          description: The audience in one sentence, to confirm with the person.
        chips:
          type: array
          items:
            $ref: "#/components/schemas/Chip"
        questions:
          type: array
          items:
            $ref: "#/components/schemas/BriefQuestion"
        unrecognised:
          type: array
          items:
            type: string
          description: Words of the brief that matched no filter, shown rather than guessed.
        quantity:
          anyOf:
            - type: integer
              minimum: 0
            - type: "null"
          description: A quantity named in the brief, such as 300, for quote_order limit.
      required:
        - audience
        - readback
        - chips
        - questions
        - unrecognised
        - quantity
      description: An audience definition from a brief, with its chips and any questions.
    CompiledPlan:
      type: object
      properties:
        engine_rules_version:
          type: string
        defaults_applied:
          type: array
          items:
            type: string
            pattern: ^[a-z0-9]+(_[a-z0-9]+)*$
          description: Defaults the server filled, such as active_companies and named_emails.
        expanded_terms:
          type: array
          items:
            $ref: "#/components/schemas/ExpandedTerm"
        centres:
          type: array
          items:
            $ref: "#/components/schemas/ResolvedCentre"
        sic_codes:
          type: array
          items:
            type: string
          description: SIC codes behind any business class filter.
        geography_sets:
          type: array
          items:
            type: string
        suppressions:
          type: array
          items:
            $ref: "#/components/schemas/SuppressionChecksum"
      required:
        - engine_rules_version
        - defaults_applied
        - expanded_terms
        - centres
        - sic_codes
        - geography_sets
        - suppressions
      description: "How the definition was run: expanded terms, resolved centres, SIC codes behind a business class and suppression checksums."
    ComplianceObject:
      type: object
      properties:
        lawful_basis:
          type: string
          enum:
            - legitimate_interests_art6_1_f
          description: UK GDPR Article 6(1)(f) legitimate interests, with the LIA in lia_ref.
        lia_ref:
          type: string
          description: The published legitimate interests assessment.
        subscriber_class:
          type: string
          enum:
            - ltd
            - plc
            - llp
            - scottish_partnership
            - public_body
            - charity_incorporated
            - sole_trader
            - ordinary_partnership
            - unknown
          description: "PECR subscriber class: ltd, plc, llp, scottish_partnership, public_body, charity_incorporated, sole_trader, ordinary_partnership, unknown."
        corporate_subscriber:
          type: boolean
          description: True for companies, LLPs, Scottish partnerships and public bodies, where the PECR regulation 22 consent rule does not apply.
        email_marketing_basis:
          type: string
          enum:
            - pecr_corporate_subscriber
            - consent_required
        subscriber_evidence:
          type: object
          properties:
            companies_house_number:
              type:
                - string
                - "null"
            company_type:
              type: string
          required:
            - companies_house_number
            - company_type
          description: The register entry behind subscriber_class.
        article14_status:
          type: string
          enum:
            - notified
            - publicly_notified_disproportionate_effort
            - pending_within_window
          description: "Article 14 notice status: notified, publicly_notified_disproportionate_effort, pending_within_window."
        article14_notified_at:
          anyOf:
            - type: string
              format: date
            - type: "null"
        notice_version:
          type: string
          description: The privacy notice version sent.
        provenance:
          $ref: "#/components/schemas/Provenance"
        tps_ctps_status:
          type: string
          enum:
            - clear
            - tps_registered
            - ctps_registered
            - tps_or_ctps_registered
            - not_screened
          description: "Telephone screen: clear, tps_registered, ctps_registered, tps_or_ctps_registered, not_screened. The result of the screen on tps_ctps_screened_at, valid until tps_ctps_valid_until. In a sample, an order's records (each time they are fetched, downloaded or pushed) and an enrichment, a clear number reads not_screened, with no dates, once that screen is more than 28 days before the day the record is served; a registered number stays registered."
        tps_ctps_screened_at:
          anyOf:
            - type: string
              format: date
            - type: "null"
          description: The date of the screen the status is as of; null when not screened.
        tps_ctps_valid_until:
          anyOf:
            - type: string
              format: date
            - type: "null"
          description: 28 days after tps_ctps_screened_at, when this screen stops being valid. Null when not screened. Screen the numbers you are supplied yourself before calling, and again every 28 days.
        suppression_status:
          type: string
          enum:
            - none
            - objected_to_emailmovers
            - objected_to_licensee
            - deceased
            - left_company
        record_age_days:
          type: integer
          minimum: 0
          description: Days since the record was compiled.
        attribution:
          description: The OGL v3.0 attribution for Companies House company data. Officer names are personal data outside the OGL, so they carry article14_status instead.
          type:
            - string
            - "null"
      required:
        - lawful_basis
        - lia_ref
        - subscriber_class
        - corporate_subscriber
        - email_marketing_basis
        - subscriber_evidence
        - article14_status
        - article14_notified_at
        - notice_version
        - provenance
        - tps_ctps_status
        - tps_ctps_screened_at
        - tps_ctps_valid_until
        - suppression_status
        - record_age_days
        - attribution
      description: Carried by every preview row, order record, lookup and enrichment, and repeated as CSV columns and as the MyDataHub property group on CRM push.
      examples:
        - lawful_basis: legitimate_interests_art6_1_f
          lia_ref: EM-LIA-2026-03
          subscriber_class: ltd
          corporate_subscriber: true
          email_marketing_basis: pecr_corporate_subscriber
          subscriber_evidence:
            companies_house_number: "05046417"
            company_type: ltd
          article14_status: notified
          article14_notified_at: "2026-06-14"
          notice_version: 2026-02
          provenance:
            company:
              source: companies_house
              as_of: "2026-09-21"
            contact:
              source: emailmovers_research
              compiled_at: "2026-05-02"
              last_verified_at: "2026-08-30"
              method: telephone
            email:
              verified_at: "2026-09-10"
              result: ok
              provider_location: EU
          tps_ctps_status: clear
          tps_ctps_screened_at: "2026-09-12"
          tps_ctps_valid_until: "2026-10-10"
          suppression_status: none
          record_age_days: 147
          attribution: Contains public sector information licensed under the Open Government Licence v3.0
    ComplianceReceipt:
      type: object
      properties:
        id:
          type: string
          pattern: ^rcpt_[0-7][0-9A-HJKMNP-TV-Z]{25}$
          description: "Format: a compliance receipt id: rcpt_ followed by a 26 character ULID in upper case Crockford base32, such as rcpt_01JB4NA2Q5W8ZAK8Q2V7XN3M9R."
          examples:
            - rcpt_01JB4NA2Q5W8ZAK8Q2V7XN3M9R
        order_id:
          type: string
          pattern: ^ord_[0-7][0-9A-HJKMNP-TV-Z]{25}$
          description: "Format: an order id: ord_ followed by a 26 character ULID in upper case Crockford base32, such as ord_01JB4N9S3W2X6Y7Z8A1B2C3D4E."
          examples:
            - ord_01JB4N9S3W2X6Y7Z8A1B2C3D4E
        jws:
          type: string
          pattern: ^[A-Za-z0-9_-]+\.[A-Za-z0-9_-]+\.[A-Za-z0-9_-]+$
          description: The receipt as a compact JWS signed with ES256.
        alg:
          type: string
          const: ES256
        kid:
          type: string
          description: The signing key id in jwks_url.
        jwks_url:
          type: string
          format: uri
        pdf_url:
          type: string
          format: uri
          description: The PDF twin at mydatahub.emailmovers.com/receipts/{id}.
        claims:
          $ref: "#/components/schemas/ReceiptClaims"
      required:
        - id
        - order_id
        - jws
        - alg
        - kid
        - jwks_url
        - pdf_url
        - claims
      description: A signed compliance receipt for an order, verifiable by anyone with POST /v1/compliance/receipts/verify.
    ComplianceSummary:
      type: object
      properties:
        lawful_basis:
          type: string
          enum:
            - legitimate_interests_art6_1_f
        corporate_subscriber:
          type: integer
          minimum: 0
          description: Records at corporate subscribers.
        individual_subscriber_excluded:
          type: integer
          minimum: 0
          description: Sole traders and ordinary partnerships left out of an email audience.
        tps_ctps_excluded:
          type: integer
          minimum: 0
          description: TPS or CTPS registered numbers left out when phone is requested.
        article14_notified:
          type: integer
          minimum: 0
        article14_pending_within_window:
          type: integer
          minimum: 0
          description: Records inside the one month Article 14 window, not yet notified.
        suppressed_removed:
          type: array
          items:
            $ref: "#/components/schemas/SuppressionRemoval"
        jurisdiction_excluded:
          description: "Partner datasets only: records the jurisdiction policy table left out (Germany, Canada and California by policy), reported like suppression removals."
          type: array
          items:
            $ref: "#/components/schemas/JurisdictionExclusion"
      required:
        - lawful_basis
        - corporate_subscriber
        - individual_subscriber_excluded
        - tps_ctps_excluded
        - article14_notified
        - article14_pending_within_window
        - suppressed_removed
      description: Beside every count, so a person or an agent can reason before spending. Individual subscribers and TPS and CTPS registered numbers are excluded by default, as visible toggles; numbers not screened are kept.
    ConfirmationRequiredProblem:
      type: object
      properties:
        type:
          type: string
          format: uri
          description: A URI under https://emailmovers.com/mydatahub/docs/errors/ naming the problem type.
        title:
          type: string
          description: A short summary of the problem type.
        status:
          type: integer
          minimum: 400
          maximum: 599
        detail:
          type: string
          description: What happened on this request, in plain English.
        instance:
          description: The request path or a request id this problem belongs to.
          type: string
        code:
          type: string
          const: confirmation-required
        next_step:
          type: string
          description: What to do next, in plain English.
        did_you_mean:
          description: The closest valid values, best first.
          type: array
          items:
            type: string
        errors:
          description: "For validation-failed: every problem found, each with a JSON pointer."
          type: array
          items:
            $ref: "#/components/schemas/ProblemError"
        required_scope:
          description: "For insufficient-scope: the scope to request, such as orders:write."
          type: string
        retry_after_seconds:
          description: "For rate-limited and service-unavailable: the same value as the Retry-After header."
          type: integer
          minimum: 0
        action:
          type: string
          enum:
            - order
            - enrichment
            - live_push
          description: "What needs confirming: an order, an enrichment or a live CRM push."
        quote_id:
          description: "For an order: the machine quote to keep. A person can approve it later."
          type: string
          pattern: ^qte_[0-7][0-9A-HJKMNP-TV-Z]{25}$
          examples:
            - qte_01JB4M2R6T1Y5W8ZAK8Q2V7XN3
        approval_url:
          type: string
          format: uri
          description: A page on mydatahub.emailmovers.com where the signed-in person approves, such as mydatahub.emailmovers.com/quotes/{id}/approve. It returns a confirmation_token bound to that person (their sub), this spend and an expiry.
        expires_at:
          description: "When the approval page stops accepting: the machine quote's expiry."
          type: string
          format: date-time
      required:
        - type
        - title
        - status
        - detail
        - code
        - next_step
        - action
        - approval_url
      description: "403: no human confirmation came with a spend, and no agent key covers it. A standard API key or an OAuth token alone never spends. Nothing was charged and no order was created."
    ConfirmedBy:
      type: object
      properties:
        method:
          type: string
          enum:
            - elicitation_form
            - approval_url
            - agent_key
            - app
          description: elicitation_form (the MCP form), approval_url (the approval page's confirmation_token), agent_key (a pre-authorised agent key within its limits) or app (a person ordering in the app itself, a first-party session; third-party OAuth clients never count).
        key_id:
          description: The agent key, for agent_key.
          type: string
          pattern: ^key_[0-7][0-9A-HJKMNP-TV-Z]{25}$
          examples:
            - key_01JB3Z4Q2V7XN3M9R6T1Y5W8ZA
        user_id:
          description: The person who confirmed.
          type: string
          pattern: ^usr_[0-7][0-9A-HJKMNP-TV-Z]{25}$
          examples:
            - usr_01JA0Q3V7XN3M9R6T1Y5W8ZAK8
        client_id:
          description: The OAuth client, such as a Client ID Metadata Document URL.
          type: string
        user_agent:
          description: The calling client's User-Agent.
          type:
            - string
            - "null"
        mtls_certificate_fingerprint:
          description: SHA-256 of the client certificate when one was presented (ChatGPT's mTLS).
          type:
            - string
            - "null"
        web_bot_auth_signature_agent:
          description: The Signature-Agent of a verified Web Bot Auth signature, when present.
          type:
            - string
            - "null"
        confirmed_at:
          type: string
          format: date-time
      required:
        - method
      description: Who approved the spend and from which client, recorded in the audit trail and on invoices.
    Connection:
      type: object
      properties:
        id:
          type: string
          pattern: ^conn_[0-7][0-9A-HJKMNP-TV-Z]{25}$
          description: "Format: a CRM connection id: conn_ followed by a 26 character ULID in upper case Crockford base32, such as conn_01JB3Y2X6Y7Z8A1B2C3D4E5F6G."
          examples:
            - conn_01JB3Y2X6Y7Z8A1B2C3D4E5F6G
        provider:
          type: string
          enum:
            - hubspot
            - salesforce
            - google_sheets
            - deliverite
            - webhook
            - csv
        name:
          type: string
        status:
          type: string
          enum:
            - pending_authorisation
            - connected
            - expired
            - revoked
        authorise_url:
          anyOf:
            - type: string
              format: uri
            - type: "null"
          description: Where a person authorises the connection in the app, while pending_authorisation.
        read_only_overlap:
          type: boolean
          description: Whether the connection may read email hashes for the New to you overlap check.
        created_at:
          type: string
          format: date-time
        last_used_at:
          anyOf:
            - type: string
              format: date-time
            - type: "null"
      required:
        - id
        - provider
        - name
        - status
        - authorise_url
        - read_only_overlap
        - created_at
        - last_used_at
      description: A CRM or destination connection.
    ConnectionExpiredEvent:
      type: object
      properties:
        id:
          type: string
          pattern: ^evt_[0-7][0-9A-HJKMNP-TV-Z]{25}$
          description: Also sent as the webhook-id header; your idempotency key.
          examples:
            - evt_01JB4NB7T1Y5W8ZAK8Q2V7XN3M
        type:
          type: string
          const: connection.expired
        timestamp:
          type: string
          format: date-time
        api_version:
          type: string
          pattern: ^[0-9]{4}-(0[1-9]|1[0-2])-(0[1-9]|[12][0-9]|3[01])$
          description: A dated MyDataHub-Version, such as 2027-01-15.
        data:
          type: object
          properties:
            connection_id:
              type: string
              pattern: ^conn_[0-7][0-9A-HJKMNP-TV-Z]{25}$
              description: "Format: a CRM connection id: conn_ followed by a 26 character ULID in upper case Crockford base32, such as conn_01JB3Y2X6Y7Z8A1B2C3D4E5F6G."
              examples:
                - conn_01JB3Y2X6Y7Z8A1B2C3D4E5F6G
            provider:
              type: string
            reconnect_url:
              type: string
              format: uri
          required:
            - connection_id
            - provider
            - reconnect_url
      required:
        - id
        - type
        - timestamp
        - api_version
        - data
      description: A CRM token failed; reconnect in the app.
    ConnectionList:
      type: object
      properties:
        data:
          type: array
          items:
            $ref: "#/components/schemas/Connection"
        has_more:
          type: boolean
          description: Whether another page follows.
        next_cursor:
          anyOf:
            - type: string
              minLength: 1
              maxLength: 512
              description: An opaque cursor from next_cursor. Pass it back unchanged.
            - type: "null"
          description: Pass as cursor to fetch the next page; null on the last page.
      required:
        - data
        - has_more
        - next_cursor
      description: A page of CRM connections, newest first.
    ConnectionRequest:
      type: object
      properties:
        provider:
          type: string
          enum:
            - hubspot
            - salesforce
            - google_sheets
            - deliverite
            - webhook
            - csv
        name:
          type: string
          minLength: 1
          maxLength: 100
        webhook_url:
          description: "Provider webhook only, and required there: the https endpoint to post to."
          type: string
          format: uri
          pattern: ^https://
      required:
        - provider
        - name
      additionalProperties: false
      description: A new connection. OAuth providers return an authorise_url for a person to approve; provider webhook takes webhook_url.
      allOf:
        - if:
            type: object
            properties:
              provider:
                const: webhook
            required:
              - provider
          then:
            type: object
            properties:
              webhook_url: true
            required:
              - webhook_url
          else:
            type: object
            not:
              type: object
              properties:
                webhook_url: true
              required:
                - webhook_url
    ContactRecord:
      type: object
      properties:
        id:
          type: string
          pattern: ^contact_[0-7][0-9A-HJKMNP-TV-Z]{25}$
          description: The record id, pushed to CRMs as mydatahub_record_id.
          examples:
            - contact_01JA9X2V7XN3M9R6T1Y5W8ZAK8
        dataset_version:
          type: string
          pattern: ^[0-9]{4}-W(0[1-9]|[1-4][0-9]|5[0-3])$
          description: The immutable weekly build a count is bound to, such as 2026-W39. The last 52 stay queryable.
          examples:
            - 2026-W39
        contact:
          type: object
          properties:
            first_name:
              type: string
            last_name:
              type: string
            title:
              type: string
            title_family:
              type:
                - string
                - "null"
            job_function:
              type:
                - string
                - "null"
            job_level_rank:
              anyOf:
                - type: integer
                  minimum: 1
                  maximum: 13
                - type: "null"
              description: Rank on the 13-rank ladder, 1 (Owner) the most senior to 13 (Volunteer); null when the level is not known.
            job_level:
              description: The level, such as Director; null when the level is not known.
              type:
                - string
                - "null"
          required:
            - first_name
            - last_name
            - title
            - title_family
            - job_function
            - job_level_rank
            - job_level
        email:
          anyOf:
            - type: object
              properties:
                address:
                  type: string
                  format: email
                type:
                  type: string
                  enum:
                    - named
                    - generic_local_part
                    - generic_domain
                deliverability_score:
                  type: integer
                  minimum: 0
                  maximum: 100
                verified_at:
                  anyOf:
                    - type: string
                      format: date
                    - type: "null"
                result:
                  type: string
                  enum:
                    - ok
                    - catch_all
                    - unknown
                    - invalid
                    - disposable
              required:
                - address
                - type
                - deliverability_score
                - verified_at
                - result
            - type: "null"
        phone:
          anyOf:
            - type: object
              properties:
                "e164":
                  type: string
                  pattern: ^\+[1-9][0-9]{7,14}$
                line_type:
                  type: string
                  enum:
                    - landline
                    - mobile
                    - voip
                    - unknown
                tps_ctps_status:
                  type: string
                  enum:
                    - clear
                    - tps_registered
                    - ctps_registered
                    - tps_or_ctps_registered
                    - not_screened
                screened_at:
                  anyOf:
                    - type: string
                      format: date
                    - type: "null"
              required:
                - "e164"
                - line_type
                - tps_ctps_status
                - screened_at
            - type: "null"
          description: Present when the field set includes telephone and a number is held.
        company:
          type: object
          properties:
            name:
              type: string
            companies_house_number:
              type:
                - string
                - "null"
            status:
              type: string
            type:
              type: string
            subscriber_class:
              type: string
              enum:
                - ltd
                - plc
                - llp
                - scottish_partnership
                - public_body
                - charity_incorporated
                - sole_trader
                - ordinary_partnership
                - unknown
            sic_2007:
              type: array
              items:
                $ref: "#/components/schemas/SicCode"
            employee_band:
              type:
                - string
                - "null"
            turnover_band:
              type:
                - string
                - "null"
            website_domain:
              type:
                - string
                - "null"
          required:
            - name
            - companies_house_number
            - status
            - type
            - subscriber_class
            - sic_2007
            - employee_band
            - turnover_band
            - website_domain
        site:
          type: object
          properties:
            address_lines:
              type: array
              items:
                type: string
            town:
              type:
                - string
                - "null"
            county:
              type:
                - string
                - "null"
            postcode:
              type: string
            region:
              type: string
          required:
            - address_lines
            - town
            - county
            - postcode
            - region
        compliance:
          $ref: "#/components/schemas/ComplianceObject"
        licence_expires_at:
          anyOf:
            - type: string
              format: date
            - type: "null"
      required:
        - id
        - dataset_version
        - contact
        - email
        - phone
        - company
        - site
        - compliance
        - licence_expires_at
      description: "One complete UK record: named email, telephone where held, postal address and company fields with Companies House number and SIC, plus the compliance object."
    Count:
      type: object
      properties:
        id:
          type: string
          pattern: ^cnt_[A-Z2-7]{20}$
          description: "Format: a count id: cnt_ followed by 20 base32 characters (A to Z and 2 to 7), such as cnt_7K2PQ4MAXN3R6TBY5WDZ."
          examples:
            - cnt_7K2PQ4MAXN3R6TBY5WDZ
        dataset:
          type: string
          enum:
            - uk_contacts
            - ie_contacts
            - global_contacts
          description: The dataset an audience draws from. uk_contacts is live; ie_contacts and global_contacts are listed but not yet served.
        dataset_version:
          type: string
          pattern: ^[0-9]{4}-W(0[1-9]|[1-4][0-9]|5[0-3])$
          description: The immutable weekly build a count is bound to, such as 2026-W39. The last 52 stay queryable.
          examples:
            - 2026-W39
        engine_rules_version:
          type: string
          minLength: 1
          description: The engine rules (title expansion, defaults, geography sets) the count ran under; part of what the count id hashes, such as 2026.09.1.
        as_of:
          description: The date the dataset version was built, for as-of statements.
          type: string
          format: date
        level:
          type: string
          enum:
            - contact
            - company
          description: contact (the default) or company. Contacts and companies come back together either way.
        credits_per_record:
          type: number
          minimum: 0
          description: "Credits, to one decimal place: whole at 1 credit a record, fractional only where a rule is (0.1 company-only, 1.8 outright, both proposed)."
        contacts:
          type: integer
          minimum: 0
          description: "Total: every matching contact."
        companies:
          type: integer
          minimum: 0
        emailable:
          type: integer
          minimum: 0
          description: A named email scoring 70 or more at a corporate subscriber.
        new_to_you:
          anyOf:
            - type: integer
              minimum: 0
            - type: "null"
          description: Excludes your earlier orders and CRM overlap; null when signed out.
        premium:
          anyOf:
            - type: integer
              minimum: 0
            - type: "null"
          description: "Premium: the contacts of all_records that are premium records (contact.premium true). At launch, a premium record is in the premium export of a stated date. Null when the dataset version carries no premium data, or withheld when the premium records are at a single company and the caller has no account; premium_note says which."
        all_records:
          type: integer
          minimum: 0
          description: "All records: every matching contact with the audience's top-level premium filters taken out (a premium filter inside any or not stays), so the premium figure always has the full figure beside it. The same as contacts when the audience has no premium filter."
        premium_note:
          type: string
          minLength: 1
          description: "What premium means on this dataset version, or why premium is null: no premium data on the version, or withheld because the premium records are at a single company."
        views:
          $ref: "#/components/schemas/CountViews"
        quality:
          $ref: "#/components/schemas/Quality"
        breakdowns:
          type: array
          items:
            $ref: "#/components/schemas/Breakdown"
        crosstab:
          anyOf:
            - $ref: "#/components/schemas/Crosstab"
            - type: "null"
        compliance_summary:
          $ref: "#/components/schemas/ComplianceSummary"
        defaults:
          type: array
          items:
            $ref: "#/components/schemas/CountDefault"
          description: "Every default with the number behind its toggle: dissolved and insolvent, generic emails, individual subscribers, TPS and CTPS registered."
        estimate:
          $ref: "#/components/schemas/PriceEstimate"
        expanded_terms:
          description: Every title a matches filter covered, with a count per term.
          type: array
          items:
            $ref: "#/components/schemas/ExpandedTerm"
        disclosure:
          $ref: "#/components/schemas/Disclosure"
        effective_audience:
          description: The definition after defaults and bundles, as hashed into the id.
          $ref: "#/components/schemas/Audience"
        plan:
          $ref: "#/components/schemas/CompiledPlan"
        current:
          description: "On a replay: the same audience on the current dataset version."
          anyOf:
            - $ref: "#/components/schemas/CurrentCount"
            - type: "null"
        removed_since:
          description: "On a replay: records since removed by objections and erasure."
          type: integer
          minimum: 0
        warnings:
          type: array
          items:
            type: string
        created_at:
          type: string
          format: date-time
        expires_at:
          type: string
          format: date-time
          description: The id replays until then (12 months).
      required:
        - id
        - dataset
        - dataset_version
        - engine_rules_version
        - credits_per_record
        - contacts
        - companies
        - emailable
        - new_to_you
        - premium
        - all_records
        - premium_note
        - quality
        - breakdowns
        - compliance_summary
        - defaults
        - estimate
        - disclosure
        - expires_at
      description: Total, New to you and Emailable, the premium figure beside all records, with companies, breakdowns, quality, the compliance summary, the defaults behind the toggles, disclosure and a price estimate. The id is content addressed, so the same definition from chips, REST or MCP gives the same id.
    CountDefault:
      type: object
      properties:
        id:
          type: string
          enum:
            - active_companies
            - named_emails
            - corporate_subscribers
            - no_registered_phones
          description: active_companies, named_emails, corporate_subscribers or no_registered_phones.
        applied:
          type: boolean
          description: False when the audience filters the same field itself, or turned it off.
        views:
          type: array
          items:
            type: string
            enum:
              - total
              - new_to_you
              - emailable
          description: The headline views the default shapes; named emails and corporate subscribers shape Emailable only.
        removed:
          description: "What the toggle shows: the dissolved and insolvent count, the generic (info@, sales@) count, the individual subscriber count or the TPS and CTPS registered count."
          $ref: "#/components/schemas/CellCount"
        screened_at:
          description: "no_registered_phones only: the version's TPS and CTPS screen date."
          anyOf:
            - type: string
              format: date
            - type: "null"
      required:
        - id
        - applied
        - views
        - removed
      description: A default the count applied, and the records it removed, for its toggle.
    CountReplay:
      type: object
      properties:
        id:
          type: string
          pattern: ^cnt_[A-Z2-7]{20}$
          description: "Format: a count id: cnt_ followed by 20 base32 characters (A to Z and 2 to 7), such as cnt_7K2PQ4MAXN3R6TBY5WDZ."
          examples:
            - cnt_7K2PQ4MAXN3R6TBY5WDZ
        dataset:
          type: string
          enum:
            - uk_contacts
            - ie_contacts
            - global_contacts
          description: The dataset an audience draws from. uk_contacts is live; ie_contacts and global_contacts are listed but not yet served.
        dataset_version:
          type: string
          pattern: ^[0-9]{4}-W(0[1-9]|[1-4][0-9]|5[0-3])$
          description: The immutable weekly build a count is bound to, such as 2026-W39. The last 52 stay queryable.
          examples:
            - 2026-W39
        engine_rules_version:
          type: string
          minLength: 1
          description: The engine rules (title expansion, defaults, geography sets) the count ran under; part of what the count id hashes, such as 2026.09.1.
        as_of:
          description: The date the dataset version was built, for as-of statements.
          type: string
          format: date
        level:
          type: string
          enum:
            - contact
            - company
          description: contact (the default) or company. Contacts and companies come back together either way.
        credits_per_record:
          type: number
          minimum: 0
          description: "Credits, to one decimal place: whole at 1 credit a record, fractional only where a rule is (0.1 company-only, 1.8 outright, both proposed)."
        contacts:
          type: integer
          minimum: 0
          description: "Total: every matching contact."
        companies:
          type: integer
          minimum: 0
        emailable:
          type: integer
          minimum: 0
          description: A named email scoring 70 or more at a corporate subscriber.
        new_to_you:
          anyOf:
            - type: integer
              minimum: 0
            - type: "null"
          description: Excludes your earlier orders and CRM overlap; null when signed out.
        premium:
          anyOf:
            - type: integer
              minimum: 0
            - type: "null"
          description: "Premium: the contacts of all_records that are premium records (contact.premium true). At launch, a premium record is in the premium export of a stated date. Null when the dataset version carries no premium data, or withheld when the premium records are at a single company and the caller has no account; premium_note says which."
        all_records:
          type: integer
          minimum: 0
          description: "All records: every matching contact with the audience's top-level premium filters taken out (a premium filter inside any or not stays), so the premium figure always has the full figure beside it. The same as contacts when the audience has no premium filter."
        premium_note:
          type: string
          minLength: 1
          description: "What premium means on this dataset version, or why premium is null: no premium data on the version, or withheld because the premium records are at a single company."
        views:
          $ref: "#/components/schemas/CountViews"
        quality:
          $ref: "#/components/schemas/Quality"
        breakdowns:
          type: array
          items:
            $ref: "#/components/schemas/Breakdown"
        crosstab:
          anyOf:
            - $ref: "#/components/schemas/Crosstab"
            - type: "null"
        compliance_summary:
          $ref: "#/components/schemas/ComplianceSummary"
        defaults:
          type: array
          items:
            $ref: "#/components/schemas/CountDefault"
          description: "Every default with the number behind its toggle: dissolved and insolvent, generic emails, individual subscribers, TPS and CTPS registered."
        estimate:
          $ref: "#/components/schemas/PriceEstimate"
        expanded_terms:
          description: Every title a matches filter covered, with a count per term.
          type: array
          items:
            $ref: "#/components/schemas/ExpandedTerm"
        disclosure:
          $ref: "#/components/schemas/Disclosure"
        effective_audience:
          description: The definition after defaults and bundles, as hashed into the id.
          $ref: "#/components/schemas/Audience"
        plan:
          $ref: "#/components/schemas/CompiledPlan"
        current:
          anyOf:
            - $ref: "#/components/schemas/CurrentCount"
            - type: "null"
          description: The same audience on the current dataset version; null when the pinned version is current.
        removed_since:
          type: integer
          minimum: 0
          description: Records since removed by objections and erasure.
        warnings:
          type: array
          items:
            type: string
        created_at:
          type: string
          format: date-time
        expires_at:
          type: string
          format: date-time
          description: The id replays until then (12 months).
      required:
        - id
        - dataset
        - dataset_version
        - engine_rules_version
        - credits_per_record
        - contacts
        - companies
        - emailable
        - new_to_you
        - premium
        - all_records
        - premium_note
        - views
        - quality
        - breakdowns
        - compliance_summary
        - defaults
        - estimate
        - disclosure
        - effective_audience
        - plan
        - current
        - removed_since
        - created_at
        - expires_at
      description: "A count replayed by id for 12 months: the effective definition, the compiled plan, the pinned dataset and engine rules versions, all three views with premium and all records, the breakdowns, the compliance summary, quality, the estimate, created_at and the number on the current dataset."
    CountRequest:
      type: object
      properties:
        audience:
          $ref: "#/components/schemas/AudienceRef"
        level:
          default: contact
          type: string
          enum:
            - contact
            - company
          description: contact (the default) or company. Contacts and companies come back together either way.
        breakdown_by:
          maxItems: 3
          type: array
          items:
            type: string
            enum:
              - company.sic_2007
              - company.business_class
              - company.employee_band
              - company.turnover_band
              - company.fleet_band
              - company.type
              - company.status
              - company.subscriber_class
              - site.region
              - site.county
              - site.town
              - site.postcode
              - contact.title_family
              - contact.job_function
              - contact.job_level_rank
              - email.exists
              - email.type
              - phone.exists
              - phone.tps_ctps_status
            description: A field a count can be broken down by.
          description: Up to three fields to break the count down by, each once, such as industry by seniority by region. Returns the top 50 cells per field plus other.
          uniqueItems: true
      required:
        - audience
      additionalProperties: false
      description: An audience, a level and up to three breakdown fields. Free, and needs no account.
    CountView:
      type: object
      properties:
        contacts:
          type: integer
          minimum: 0
        companies:
          type: integer
          minimum: 0
      required:
        - contacts
        - companies
      description: Contacts and companies in one headline view.
    CountViews:
      type: object
      properties:
        total:
          $ref: "#/components/schemas/CountView"
        new_to_you:
          anyOf:
            - $ref: "#/components/schemas/CountView"
            - type: "null"
          description: Removes records from earlier orders and a connected CRM; null when signed out.
        emailable:
          description: A named email scoring 70 or more at a corporate subscriber.
          $ref: "#/components/schemas/CountView"
        premium:
          anyOf:
            - $ref: "#/components/schemas/CountView"
            - type: "null"
          description: The premium records of all_records (contact.premium true); null when the dataset version carries no premium data, or withheld when they are at a single company and the caller has no account.
        all_records:
          description: "The full figure: the audience with its top-level premium filters taken out (one inside any or not stays). The same as total when it has none."
          $ref: "#/components/schemas/CountView"
      required:
        - total
        - new_to_you
        - emailable
        - premium
        - all_records
      description: Contacts and companies for the three headline views, and the premium figure beside all records.
    CreditPack:
      type: object
      properties:
        id:
          type: string
          pattern: ^[a-z0-9]+(_[a-z0-9]+)*$
          description: Such as free, pay_as_you_go or plan_12_month.
        name:
          type: string
        kind:
          type: string
          const: pack
        status:
          type: string
          enum:
            - confirmed
            - proposed
            - to_confirm
          description: confirmed is in force; proposed awaits a decision; to_confirm is a figure still being checked.
        available_to:
          type: string
          enum:
            - everyone
            - new_customers
            - existing_customers
          description: everyone; new_customers only (the subscription); existing_customers only, who keep their prices (pay as you go and the 12-month plan).
        price_gbp_ex_vat:
          type: number
          minimum: 0
          description: Pounds sterling excluding VAT, to two decimal places.
        billing_period:
          type: string
          const: once
        contract_months:
          description: The contract's length in months, such as 12; absent when there is none.
          anyOf:
            - type: integer
              minimum: 0
            - type: "null"
        annual_price_gbp_ex_vat:
          anyOf:
            - type: number
              minimum: 0
              description: Pounds sterling excluding VAT, to two decimal places.
            - type: "null"
        credits:
          type: integer
          minimum: 0
          description: Credits per billing period, or once.
        per_record_gbp:
          anyOf:
            - type: number
              minimum: 0
              description: Pounds sterling excluding VAT, to two decimal places.
            - type: "null"
          description: Price per record at full use of the allowance; null for the free plan.
        rollover_cap_credits:
          description: Subscription credits roll over while subscribed, up to this many; 0 when each period's credits are used or lost.
          anyOf:
            - type: integer
              minimum: 0
            - type: "null"
        credits_expire:
          type: boolean
        for_whom:
          type: string
        notes:
          type: string
      required:
        - id
        - name
        - kind
        - status
        - available_to
        - price_gbp_ex_vat
        - billing_period
        - credits
        - per_record_gbp
        - credits_expire
        - for_whom
      description: A one-off prepaid bundle of credits, usable on any plan. Credit pack credits never expire and survive cancellation.
    CreditPurchase:
      type: object
      properties:
        id:
          type: string
          pattern: ^pur_[0-7][0-9A-HJKMNP-TV-Z]{25}$
          description: "Format: a credit purchase id: pur_ followed by a 26 character ULID in upper case Crockford base32, such as pur_01JB4R7K2V7XN3M9R6T1Y5W8ZA."
          examples:
            - pur_01JB4R7K2V7XN3M9R6T1Y5W8ZA
        status:
          type: string
          enum:
            - pending
            - paid
            - expired
          description: pending until Stripe says the payment is paid; paid once the credits are in the balance; expired when the checkout closed unpaid (nothing was taken).
        label:
          type: string
          description: What is bought, in words, numbers first.
        lines:
          type: array
          items:
            $ref: "#/components/schemas/CreditPurchaseLine"
        credits:
          type: integer
          minimum: 0
          description: Credits the purchase adds when paid.
        gbp_ex_vat:
          type: number
          minimum: 0
          description: Pounds sterling excluding VAT, to two decimal places.
        vat_rate_percent:
          type: number
          minimum: 0
          maximum: 100
        vat_gbp:
          type: number
          minimum: 0
          description: Pounds sterling, to two decimal places.
        total_gbp:
          type: number
          minimum: 0
          description: What the card is charged, VAT included.
        currency:
          type: string
          const: GBP
        gbp_per_credit:
          type: number
          minimum: 0
          description: What one credit of this purchase cost, excluding VAT, to six decimal places. Orders value the credits they spend at it.
        credits_added:
          type: integer
          minimum: 0
          description: 0 until the payment is paid.
        checkout_url:
          anyOf:
            - type: string
              format: uri
            - type: "null"
          description: Stripe Checkout, while the purchase is pending; null once paid or expired.
        test_mode:
          type: boolean
          description: "Made with a Stripe test key: test cards only, and no money moves."
        created_at:
          type: string
          format: date-time
        expires_at:
          anyOf:
            - type: string
              format: date-time
            - type: "null"
          description: When the checkout closes if it is not paid.
        paid_at:
          anyOf:
            - type: string
              format: date-time
            - type: "null"
        attention:
          anyOf:
            - $ref: "#/components/schemas/PurchaseAttention"
            - type: "null"
        receipt:
          anyOf:
            - $ref: "#/components/schemas/PaymentReceipt"
            - type: "null"
          description: Once paid. The buyer's name, billing address and VAT number are shown only to a signed-in person with admin; anyone else sees them as null.
      required:
        - id
        - status
        - label
        - lines
        - credits
        - gbp_ex_vat
        - vat_rate_percent
        - vat_gbp
        - total_gbp
        - currency
        - gbp_per_credit
        - credits_added
        - checkout_url
        - test_mode
        - created_at
        - expires_at
        - paid_at
        - attention
        - receipt
      description: A purchase of credits by card through Stripe Checkout. Reading a pending purchase asks Stripe for its payment (at most once every 10 seconds), and adds the credits once, when it is paid.
    CreditPurchaseLine:
      type: object
      properties:
        id:
          type: string
          description: per_contact_scale or a credit pack id.
        quantity:
          type: integer
          minimum: 0
          description: Credits at the scale, or credit packs.
        credits:
          type: integer
          minimum: 0
        gbp_ex_vat:
          type: number
          minimum: 0
          description: Pounds sterling excluding VAT, to two decimal places.
      required:
        - id
        - quantity
        - credits
        - gbp_ex_vat
      description: One line of a purchase, priced.
    CreditPurchaseLineRequest:
      type: object
      properties:
        id:
          type: string
          maxLength: 64
          pattern: ^[a-z0-9]+(_[a-z0-9]+)*$
          description: per_contact_scale for credits at the scale, or the id of a credit pack on sale, such as credit_pack_40000 (GET /v1/plans).
        quantity:
          type: integer
          minimum: 1
          maximum: 1000000
          description: Credits at the scale (1 to 1,000,000), or credit packs (1 to 25).
      required:
        - id
        - quantity
      additionalProperties: false
      description: One thing to buy.
    CreditPurchaseList:
      type: object
      properties:
        data:
          type: array
          items:
            $ref: "#/components/schemas/CreditPurchase"
        has_more:
          type: boolean
          description: Whether another page follows.
        next_cursor:
          anyOf:
            - type: string
              minLength: 1
              maxLength: 512
              description: An opaque cursor from next_cursor. Pass it back unchanged.
            - type: "null"
          description: Pass as cursor to fetch the next page; null on the last page.
        payments:
          type: object
          properties:
            available:
              type: boolean
              description: Whether card payments are switched on for this server.
            test_mode:
              type: boolean
              description: "Stripe test mode: test cards only."
            vat_rate_percent:
              type: number
              minimum: 0
              maximum: 100
          required:
            - available
            - test_mode
            - vat_rate_percent
          description: How credits are bought here.
      required:
        - data
        - has_more
        - next_cursor
        - payments
      description: A page of credit purchases, newest first, and whether card payments are on.
    CreditPurchaseRequest:
      type: object
      properties:
        lines:
          minItems: 1
          maxItems: 5
          type: array
          items:
            $ref: "#/components/schemas/CreditPurchaseLineRequest"
          description: "What to buy: credits at the scale, credit packs, or both, as a quote's purchase option lists them. Each id at most once."
      required:
        - lines
      additionalProperties: false
      description: A top-up of credits at the per-contact scale (the size of the line picks its band), the 40,000 credit pack, or a quote's purchase option line by line.
    CreditRule:
      type: object
      properties:
        action:
          type: string
          pattern: ^[a-z0-9]+(_[a-z0-9]+)*$
        label:
          type: string
        credits:
          type: number
          minimum: 0
          description: "Credits, to one decimal place: whole at 1 credit a record, fractional only where a rule is (0.1 company-only, 1.8 outright, both proposed)."
        unit:
          type: string
          enum:
            - call
            - record
            - match
        status:
          type: string
          enum:
            - confirmed
            - proposed
            - to_confirm
          description: confirmed is in force; proposed awaits a decision; to_confirm is a figure still being checked.
        note:
          type: string
      required:
        - action
        - label
        - credits
        - unit
        - status
      description: What one action costs in credits.
    CreditsLowEvent:
      type: object
      properties:
        id:
          type: string
          pattern: ^evt_[0-7][0-9A-HJKMNP-TV-Z]{25}$
          description: Also sent as the webhook-id header; your idempotency key.
          examples:
            - evt_01JB4NB7T1Y5W8ZAK8Q2V7XN3M
        type:
          type: string
          const: credits.low
        timestamp:
          type: string
          format: date-time
        api_version:
          type: string
          pattern: ^[0-9]{4}-(0[1-9]|1[0-2])-(0[1-9]|[12][0-9]|3[01])$
          description: A dated MyDataHub-Version, such as 2027-01-15.
        data:
          type: object
          properties:
            balance:
              type: number
            threshold:
              type: integer
              minimum: 0
            top_up_url:
              type: string
              format: uri
          required:
            - balance
            - threshold
            - top_up_url
      required:
        - id
        - type
        - timestamp
        - api_version
        - data
      description: The balance crossed the configured threshold.
    CreditsToppedUpEvent:
      type: object
      properties:
        id:
          type: string
          pattern: ^evt_[0-7][0-9A-HJKMNP-TV-Z]{25}$
          description: Also sent as the webhook-id header; your idempotency key.
          examples:
            - evt_01JB4NB7T1Y5W8ZAK8Q2V7XN3M
        type:
          type: string
          const: credits.topped_up
        timestamp:
          type: string
          format: date-time
        api_version:
          type: string
          pattern: ^[0-9]{4}-(0[1-9]|1[0-2])-(0[1-9]|[12][0-9]|3[01])$
          description: A dated MyDataHub-Version, such as 2027-01-15.
        data:
          type: object
          properties:
            credits_added:
              type: integer
              minimum: 0
            balance:
              type: number
            source:
              type: string
              enum:
                - credit_pack
                - top_up
                - plan
                - refund
                - adjustment
              description: credit_pack for credit packs bought by card, top_up for credits bought at the per-contact scale (with or without credit packs), plan for a plan's credits, refund or adjustment.
            amount_gbp_ex_vat:
              anyOf:
                - type: number
                  minimum: 0
                  description: Pounds sterling excluding VAT, to two decimal places.
                - type: "null"
          required:
            - credits_added
            - balance
            - source
            - amount_gbp_ex_vat
      required:
        - id
        - type
        - timestamp
        - api_version
        - data
      description: A top-up settled.
    CrmPropertyGroup:
      type: object
      properties:
        mydatahub_record_id:
          type: string
          pattern: ^contact_[0-7][0-9A-HJKMNP-TV-Z]{25}$
          description: "The record id: a Salesforce External ID, a HubSpot unique property, a Pipedrive custom field."
          examples:
            - contact_01JA9X2V7XN3M9R6T1Y5W8ZAK8
        licence_expires_at:
          anyOf:
            - type: string
              format: date
            - type: "null"
        lawful_basis:
          type: string
          enum:
            - legitimate_interests_art6_1_f
        article14_status:
          type: string
          enum:
            - notified
            - publicly_notified_disproportionate_effort
            - pending_within_window
        tps_ctps_screened_at:
          anyOf:
            - type: string
              format: date
            - type: "null"
        subscriber_class:
          type: string
          enum:
            - ltd
            - plc
            - llp
            - scottish_partnership
            - public_body
            - charity_incorporated
            - sole_trader
            - ordinary_partnership
            - unknown
        suppression_status:
          type: string
          enum:
            - none
            - objected_to_emailmovers
            - objected_to_licensee
            - deceased
            - left_company
          description: "Kept current: an objection reaching the record updates it in the CRM."
        compliance_receipt_url:
          type: string
          format: uri
      required:
        - mydatahub_record_id
        - licence_expires_at
        - lawful_basis
        - article14_status
        - tps_ctps_screened_at
        - subscriber_class
        - suppression_status
        - compliance_receipt_url
      description: The MyDataHub property group written on every pushed record, so licence expiry and suppression are enforced in the CRM by field flags.
    Crosstab:
      type: object
      properties:
        by:
          minItems: 2
          maxItems: 3
          type: array
          items:
            type: string
            enum:
              - company.sic_2007
              - company.business_class
              - company.employee_band
              - company.turnover_band
              - company.fleet_band
              - company.type
              - company.status
              - company.subscriber_class
              - site.region
              - site.county
              - site.town
              - site.postcode
              - contact.title_family
              - contact.job_function
              - contact.job_level_rank
              - email.exists
              - email.type
              - phone.exists
              - phone.tps_ctps_status
            description: A field a count can be broken down by.
        cells:
          maxItems: 5000
          type: array
          items:
            $ref: "#/components/schemas/CrosstabCell"
        other:
          anyOf:
            - $ref: "#/components/schemas/OtherBucket"
            - type: "null"
        truncated:
          type: boolean
          description: "True when a cap cut the table: 5,000 cells, or fewer over MCP to keep a result under about 8,000 tokens."
      required:
        - by
        - cells
        - other
        - truncated
      description: Two or three fields as one grouped table (industry by seniority by region), top 50 values per field, capped at 5,000 cells. For anonymous and trial callers, secondary suppression hides enough further cells, each reading "suppressed", that no total can be differenced to reveal a cell under 10.
    CrosstabCell:
      type: object
      properties:
        values:
          minItems: 2
          maxItems: 3
          type: array
          items:
            anyOf:
              - type: string
              - type: number
              - type: boolean
        filter_values:
          description: The values filters take, when any differs from values (ranks for seniority).
          minItems: 2
          maxItems: 3
          type: array
          items:
            anyOf:
              - type: string
              - type: number
              - type: boolean
        contacts:
          $ref: "#/components/schemas/CellCount"
      required:
        - values
        - contacts
      description: One cell of a cross-tab, one value per field.
    CurrentCount:
      type: object
      properties:
        dataset_version:
          type: string
          pattern: ^[0-9]{4}-W(0[1-9]|[1-4][0-9]|5[0-3])$
          description: The immutable weekly build a count is bound to, such as 2026-W39. The last 52 stay queryable.
          examples:
            - 2026-W39
        contacts:
          type: integer
          minimum: 0
        companies:
          type: integer
          minimum: 0
      required:
        - dataset_version
        - contacts
        - companies
      description: The same audience on the current dataset version.
    Dataset:
      type: object
      properties:
        id:
          type: string
          enum:
            - uk_contacts
            - ie_contacts
            - global_contacts
          description: The dataset an audience draws from. uk_contacts is live; ie_contacts and global_contacts are listed but not yet served.
        name:
          type: string
        status:
          type: string
          enum:
            - live
            - planned
            - through_our_team
          description: live is served; planned is not yet served; through_our_team is sold through the bespoke service only.
        owner:
          type: string
        source:
          type: string
        licence:
          type: string
        refresh:
          type: string
        residency:
          type: string
        regulator:
          type: string
        warranty:
          type: string
        credits_per_record:
          type: number
          minimum: 0
          description: 1 for owned UK and Irish data.
        credits_status:
          type: string
          enum:
            - confirmed
            - proposed
        current_version:
          anyOf:
            - type: string
              pattern: ^[0-9]{4}-W(0[1-9]|[1-4][0-9]|5[0-3])$
              description: The immutable weekly build a count is bound to, such as 2026-W39. The last 52 stay queryable.
              examples:
                - 2026-W39
            - type: "null"
        versions_available:
          type: integer
          minimum: 0
          description: Immutable weekly versions still queryable (the last 52).
      required:
        - id
        - name
        - status
        - owner
        - source
        - licence
        - refresh
        - residency
        - regulator
        - warranty
        - credits_per_record
        - credits_status
        - current_version
        - versions_available
      description: A dataset with owner, source, licence, refresh, residency, regulator, warranty and credits per record.
    DatasetList:
      type: object
      properties:
        data:
          type: array
          items:
            $ref: "#/components/schemas/Dataset"
        has_more:
          type: boolean
          description: Whether another page follows.
        next_cursor:
          anyOf:
            - type: string
              minLength: 1
              maxLength: 512
              description: An opaque cursor from next_cursor. Pass it back unchanged.
            - type: "null"
          description: Pass as cursor to fetch the next page; null on the last page.
      required:
        - data
        - has_more
        - next_cursor
      description: A page of datasets, newest first.
    DatasetVersionChangelog:
      type: object
      properties:
        dataset:
          type: string
          enum:
            - uk_contacts
            - ie_contacts
            - global_contacts
          description: The dataset an audience draws from. uk_contacts is live; ie_contacts and global_contacts are listed but not yet served.
        version:
          type: string
          pattern: ^[0-9]{4}-W(0[1-9]|[1-4][0-9]|5[0-3])$
          description: The immutable weekly build a count is bound to, such as 2026-W39. The last 52 stay queryable.
          examples:
            - 2026-W39
        built_at:
          type: string
          format: date-time
        checksum:
          type: string
          description: SHA-256 over the version's files.
        row_counts:
          type: object
          properties:
            companies:
              type: integer
              minimum: 0
            sites:
              type: integer
              minimum: 0
            contacts:
              type: integer
              minimum: 0
            emails:
              type: integer
              minimum: 0
            phones:
              type: integer
              minimum: 0
          required:
            - companies
            - sites
            - contacts
            - emails
            - phones
        changes:
          type: object
          properties:
            records_added:
              type: integer
              minimum: 0
            records_retired:
              type: integer
              minimum: 0
            records_reverified:
              type: integer
              minimum: 0
            companies_dissolved:
              type: integer
              minimum: 0
            job_changes_confirmed:
              type: integer
              minimum: 0
            suppressions_applied:
              type: integer
              minimum: 0
          required:
            - records_added
            - records_retired
            - records_reverified
            - companies_dissolved
            - job_changes_confirmed
            - suppressions_applied
        coverage_pct:
          type: object
          propertyNames:
            type: string
            pattern: ^[a-z0-9]+(_[a-z0-9]+)*$
          additionalProperties:
            type: number
            minimum: 0
            maximum: 100
          description: Share of contacts holding each field, such as named_email.
        input_snapshots:
          type: array
          items:
            type: object
            properties:
              source:
                type: string
              as_of:
                type: string
                format: date
            required:
              - source
              - as_of
      required:
        - dataset
        - version
        - built_at
        - checksum
        - row_counts
        - changes
        - coverage_pct
        - input_snapshots
      description: What changed in one immutable weekly build, and the inputs it was built from.
    Disclosure:
      type: object
      properties:
        level:
          type: string
          enum:
            - exact
            - rounded
          description: exact for authenticated paid callers; rounded for anonymous and trial callers, whose counts under 100 and every cell are rounded to the nearest 5.
        rounded_to:
          anyOf:
            - type: integer
              exclusiveMinimum: 0
            - type: "null"
          description: 5 when numbers are rounded; null when they are exact.
        secondary_suppression:
          type: boolean
          description: True when cross-tab or breakdown cells of 10 or more were hidden as "suppressed" so totals cannot be differenced.
        notes:
          type: array
          items:
            type: string
      required:
        - level
        - rounded_to
        - secondary_suppression
        - notes
      description: 'Whether these numbers are exact or protected: "under 10" cells, rounding to 5 and secondary suppression for anonymous and trial callers.'
    EmailPolicy:
      type: object
      properties:
        min_deliverability_score:
          description: Lowest deliverability score counted, 0 to 100. Default 70.
          type: integer
          minimum: 0
          maximum: 100
        allow_catch_all:
          description: Whether catch-all addresses count. Default false.
          type: boolean
      additionalProperties: false
      description: "Email quality floor. Defaults: min_deliverability_score 70, allow_catch_all false."
    EnrichRequest:
      type: object
      properties:
        email:
          type: string
          format: email
        first_name:
          type: string
          minLength: 1
          maxLength: 100
        last_name:
          type: string
          minLength: 1
          maxLength: 100
        company_name:
          type: string
          minLength: 1
          maxLength: 200
        company_number:
          type: string
          pattern: ^[A-Z0-9]{8}$
          description: Eight characters in upper case, such as 05046417 or SC123456.
        domain:
          type: string
          minLength: 3
          maxLength: 253
        confirmation_token:
          type: string
          minLength: 16
          maxLength: 2000
          description: The token the approval page on mydatahub.emailmovers.com returns once the signed-in person approves. It is bound to that person (their sub), this spend (the machine quote_id, or the enrichment or push) and an expiry no later than the quote's, and it is redeemable once.
        licence_accepted_version:
          description: "The version of the Data Licence and Sharing Agreement the person accepted for this record: the account's agreements.dlsa.version. Required when a person enriches in the app; an agent key carries the acceptance made with it, and a confirmation_token the one made on the approval page."
          type: string
          minLength: 1
          maxLength: 100
      additionalProperties: false
      description: One known person to match. Send email, or first_name and last_name with company_name, company_number or domain.
      anyOf:
        - type: object
          properties:
            email: true
          required:
            - email
        - type: object
          properties:
            first_name: true
            last_name: true
          required:
            - first_name
            - last_name
          anyOf:
            - type: object
              properties:
                company_name: true
              required:
                - company_name
            - type: object
              properties:
                company_number: true
              required:
                - company_number
            - type: object
              properties:
                domain: true
              required:
                - domain
    Enrichment:
      type: object
      properties:
        matched:
          type: boolean
        match_confidence:
          anyOf:
            - type: number
              minimum: 0
              maximum: 1
            - type: "null"
        already_licensed:
          type: boolean
          description: True when you already hold the record, so it costs 0.
        record:
          anyOf:
            - $ref: "#/components/schemas/ContactRecord"
            - type: "null"
        credits_charged:
          type: number
          minimum: 0
          description: 1 on a confident match, otherwise 0.
        credits_remaining:
          type: number
          minimum: 0
          description: "Credits, to one decimal place: whole at 1 credit a record, fractional only where a rule is (0.1 company-only, 1.8 outright, both proposed)."
        cost_gbp:
          type: number
          minimum: 0
          description: Pounds sterling excluding VAT, to two decimal places.
        licence_expires_at:
          anyOf:
            - type: string
              format: date
            - type: "null"
      required:
        - matched
        - match_confidence
        - already_licensed
        - record
        - credits_charged
        - credits_remaining
        - cost_gbp
        - licence_expires_at
      description: The full record for a confident match, or no match at no charge.
    EventList:
      type: object
      properties:
        data:
          type: array
          items:
            $ref: "#/components/schemas/WebhookEvent"
        has_more:
          type: boolean
          description: Whether another page follows.
        next_cursor:
          anyOf:
            - type: string
              minLength: 1
              maxLength: 512
              description: An opaque cursor from next_cursor. Pass it back unchanged.
            - type: "null"
          description: Pass as cursor to fetch the next page; null on the last page.
      required:
        - data
        - has_more
        - next_cursor
      description: A page of events, oldest first.
    ExpandedTerm:
      type: object
      properties:
        term:
          type: string
          description: A matched title.
        from_term:
          type: string
          description: The input term it came from.
        expansion_set:
          anyOf:
            - type: string
              enum:
                - plural_singular
                - department_synonyms
                - ampersand_and
                - abbreviations
                - permutations
            - type: string
              const: input
          description: The expansion set that produced it, or input for the term itself.
        contacts:
          $ref: "#/components/schemas/CellCount"
        excluded:
          description: True when exclude_terms removed it from the audience.
          type: boolean
      required:
        - term
        - from_term
        - expansion_set
        - contacts
      description: One title a matches filter covers, with its count.
    FieldSet:
      type: object
      properties:
        id:
          type: string
          pattern: ^[a-z0-9]+(_[a-z0-9]+)*$
          description: Such as standard, standard_telephone or full.
        name:
          type: string
        description:
          type: string
        credits_per_record:
          type: number
          minimum: 0
          description: Every field set costs 1 credit a record.
        columns:
          type: array
          items:
            type: object
            properties:
              name:
                type: string
              description:
                type: string
            required:
              - name
              - description
      required:
        - id
        - name
        - description
        - credits_per_record
        - columns
      description: The named bundle of fields delivered with an order, with its exact columns.
    FieldSetList:
      type: object
      properties:
        data:
          type: array
          items:
            $ref: "#/components/schemas/FieldSet"
        has_more:
          type: boolean
          description: Whether another page follows.
        next_cursor:
          anyOf:
            - type: string
              minLength: 1
              maxLength: 512
              description: An opaque cursor from next_cursor. Pass it back unchanged.
            - type: "null"
          description: Pass as cursor to fetch the next page; null on the last page.
      required:
        - data
        - has_more
        - next_cursor
      description: A page of field sets, newest first.
    Filter:
      oneOf:
        - $ref: "#/components/schemas/InFilter"
        - $ref: "#/components/schemas/BetweenFilter"
        - $ref: "#/components/schemas/GteFilter"
        - $ref: "#/components/schemas/LteFilter"
        - $ref: "#/components/schemas/MatchesFilter"
        - $ref: "#/components/schemas/WithinRadiusFilter"
        - $ref: "#/components/schemas/InListFilter"
        - $ref: "#/components/schemas/SinceFilter"
      description: "One criterion: a field from GET /v1/catalogue/filters, an operator and its operands. Exclusions nest under not."
      discriminator:
        propertyName: op
        mapping:
          in: "#/components/schemas/InFilter"
          between: "#/components/schemas/BetweenFilter"
          gte: "#/components/schemas/GteFilter"
          lte: "#/components/schemas/LteFilter"
          matches: "#/components/schemas/MatchesFilter"
          within_radius: "#/components/schemas/WithinRadiusFilter"
          in_list: "#/components/schemas/InListFilter"
          since: "#/components/schemas/SinceFilter"
    FilterCatalogue:
      type: object
      properties:
        dataset:
          type: string
          enum:
            - uk_contacts
            - ie_contacts
            - global_contacts
          description: The dataset an audience draws from. uk_contacts is live; ie_contacts and global_contacts are listed but not yet served.
        dataset_version:
          type: string
          pattern: ^[0-9]{4}-W(0[1-9]|[1-4][0-9]|5[0-3])$
          description: The immutable weekly build a count is bound to, such as 2026-W39. The last 52 stay queryable.
          examples:
            - 2026-W39
        fields:
          type: array
          items:
            $ref: "#/components/schemas/FilterDimension"
        operators:
          type: array
          items:
            $ref: "#/components/schemas/OperatorInfo"
        datasets:
          type: array
          items:
            $ref: "#/components/schemas/Dataset"
      required:
        - dataset
        - dataset_version
        - fields
        - operators
        - datasets
      description: Every filterable field with its type, operators and value count, plus the datasets.
    FilterDimension:
      type: object
      properties:
        field:
          type: string
          description: The field name, also the dimension slug.
        group:
          type: string
          enum:
            - company
            - site
            - contact
            - email
            - phone
            - signal
        type:
          type: string
          enum:
            - enum
            - text
            - number
            - date
            - geo
            - hashed_list
            - boolean
            - band
        label:
          type: string
        description:
          type: string
        operators:
          type: array
          items:
            type: string
            enum:
              - in
              - between
              - gte
              - lte
              - matches
              - within_radius
              - in_list
              - since
        facet:
          type: boolean
          description: Usable in breakdown_by.
        value_count:
          anyOf:
            - type: integer
              minimum: 0
            - type: "null"
          description: Distinct values in this dataset version; null for free text and numbers.
        default:
          type:
            - string
            - "null"
        examples:
          type: array
          items:
            anyOf:
              - type: string
              - type: number
              - type: boolean
        values_url:
          description: Where to search the values, or null when the field has none to list.
          type:
            - string
            - "null"
      required:
        - field
        - group
        - type
        - label
        - description
        - operators
        - facet
        - value_count
        - default
        - examples
        - values_url
      description: One filterable field.
    FilterValue:
      type: object
      properties:
        code:
          description: The value to use in a filter.
          anyOf:
            - type: string
            - type: number
            - type: boolean
        label:
          type: string
        contacts:
          $ref: "#/components/schemas/CellCount"
        companies:
          $ref: "#/components/schemas/CellCount"
        parent:
          description: The parent code for hierarchical fields, such as SIC division 62 under J.
          type:
            - string
            - "null"
      required:
        - code
        - label
        - contacts
        - companies
      description: One value of a field, with its counts.
    FilterValueList:
      type: object
      properties:
        field:
          type: string
        dataset_version:
          type: string
          pattern: ^[0-9]{4}-W(0[1-9]|[1-4][0-9]|5[0-3])$
          description: The immutable weekly build a count is bound to, such as 2026-W39. The last 52 stay queryable.
          examples:
            - 2026-W39
        data:
          type: array
          items:
            $ref: "#/components/schemas/FilterValue"
        has_more:
          type: boolean
        next_cursor:
          anyOf:
            - type: string
              minLength: 1
              maxLength: 512
              description: An opaque cursor from next_cursor. Pass it back unchanged.
            - type: "null"
        did_you_mean:
          type: array
          items:
            type: string
        disclosure:
          $ref: "#/components/schemas/Disclosure"
      required:
        - field
        - dataset_version
        - data
        - has_more
        - next_cursor
        - disclosure
      description: A page of values in one field, best match first when q is given.
    GteFilter:
      type: object
      properties:
        field:
          type: string
          enum:
            - company.employee_band
            - company.turnover_band
            - company.fleet_band
            - contact.job_level_rank
            - email.deliverability_score
        op:
          type: string
          const: gte
        value:
          anyOf:
            - type: string
              minLength: 1
              maxLength: 200
            - type: number
          description: A band code or a number from the field's order.
      required:
        - field
        - op
        - value
      additionalProperties: false
      description: "The field is at or after the value in its order: for bands, the band and every larger one."
      examples:
        - field: company.employee_band
          op: gte
          value: 50-99
      allOf:
        - if:
            type: object
            properties:
              field:
                const: company.employee_band
            required:
              - field
          then:
            type: object
            properties:
              value:
                type: string
                enum:
                  - 1-5
                  - 6-9
                  - 10-19
                  - 20-49
                  - 50-99
                  - 100-199
                  - 200-349
                  - 350-499
                  - 500-999
                  - 1000-1499
                  - 1500-4999
                  - 5000-9999
                  - 10000+
        - if:
            type: object
            properties:
              field:
                const: company.turnover_band
            required:
              - field
          then:
            type: object
            properties:
              value:
                type: string
                enum:
                  - 1-100k
                  - 101k-200k
                  - 201k-500k
                  - 501k-750k
                  - 751k-1M
                  - 1.1M-2M
                  - 2.1M-5M
                  - 5.1M-7.5M
                  - 7.6M-10M
                  - 11M-20M
                  - 21M-50M
                  - 51M-75M
                  - 76M-100M
                  - 100M-250M
                  - 251M-500M
                  - 501M-1B
                  - 1B+
        - if:
            type: object
            properties:
              field:
                const: company.fleet_band
            required:
              - field
          then:
            type: object
            properties:
              value:
                type: string
                enum:
                  - 1-2
                  - 3-5
                  - 6-9
                  - 10-19
                  - 20-49
                  - 50-99
                  - 100+
        - if:
            type: object
            properties:
              field:
                const: contact.job_level_rank
            required:
              - field
          then:
            type: object
            properties:
              value:
                type: integer
                enum:
                  - 1
                  - 2
                  - 3
                  - 4
                  - 5
                  - 6
                  - 7
                  - 8
                  - 9
                  - 10
                  - 11
                  - 12
                  - 13
        - if:
            type: object
            properties:
              field:
                const: email.deliverability_score
            required:
              - field
          then:
            type: object
            properties:
              value:
                type: integer
                minimum: 0
                maximum: 100
    Health:
      type: object
      properties:
        status:
          type: string
          enum:
            - ok
            - degraded
            - down
        version:
          type: string
          description: The API release, such as 2.0.0.
        api_version:
          type: string
          description: The latest MyDataHub-Version, such as 2027-01-15.
        dataset_version:
          type: string
          pattern: ^[0-9]{4}-W(0[1-9]|[1-4][0-9]|5[0-3])$
          description: The immutable weekly build a count is bound to, such as 2026-W39. The last 52 stay queryable.
          examples:
            - 2026-W39
        tps_screen:
          $ref: "#/components/schemas/TpsScreen"
        time:
          type: string
          format: date-time
        status_page:
          type: string
          format: uri
      required:
        - status
        - version
        - api_version
        - dataset_version
        - tps_screen
        - time
        - status_page
      description: Whether the API is up, and which versions it serves.
    InFilter:
      type: object
      properties:
        field:
          type: string
          enum:
            - company.sic_2007
            - company.business_class
            - company.employee_band
            - company.turnover_band
            - company.fleet_band
            - company.type
            - company.status
            - company.subscriber_class
            - company.companies_house_number
            - site.region
            - site.county
            - site.town
            - site.postcode
            - contact.title_family
            - contact.job_function
            - contact.job_level_rank
            - contact.premium
            - email.exists
            - email.type
            - phone.exists
            - phone.tps_ctps_status
        op:
          type: string
          const: in
        values:
          minItems: 1
          maxItems: 10000
          type: array
          items:
            anyOf:
              - type: string
                minLength: 1
                maxLength: 200
              - type: number
              - type: boolean
            description: A code, a rank or a boolean, as the field's catalogue lists it.
      required:
        - field
        - op
        - values
      additionalProperties: false
      description: The field holds one of the listed codes.
      examples:
        - field: company.sic_2007
          op: in
          values:
            - C
      allOf:
        - if:
            type: object
            properties:
              field:
                const: company.sic_2007
            required:
              - field
          then:
            type: object
            properties:
              values:
                type: array
                items:
                  type: string
                  pattern: ^([A-U]|[0-9]{2,5})$
        - if:
            type: object
            properties:
              field:
                const: company.business_class
            required:
              - field
          then:
            type: object
            properties:
              values:
                type: array
                items:
                  type: string
                  minLength: 1
        - if:
            type: object
            properties:
              field:
                const: company.employee_band
            required:
              - field
          then:
            type: object
            properties:
              values:
                type: array
                items:
                  type: string
                  enum:
                    - 1-5
                    - 6-9
                    - 10-19
                    - 20-49
                    - 50-99
                    - 100-199
                    - 200-349
                    - 350-499
                    - 500-999
                    - 1000-1499
                    - 1500-4999
                    - 5000-9999
                    - 10000+
                    - unclassified
        - if:
            type: object
            properties:
              field:
                const: company.turnover_band
            required:
              - field
          then:
            type: object
            properties:
              values:
                type: array
                items:
                  type: string
                  enum:
                    - 1-100k
                    - 101k-200k
                    - 201k-500k
                    - 501k-750k
                    - 751k-1M
                    - 1.1M-2M
                    - 2.1M-5M
                    - 5.1M-7.5M
                    - 7.6M-10M
                    - 11M-20M
                    - 21M-50M
                    - 51M-75M
                    - 76M-100M
                    - 100M-250M
                    - 251M-500M
                    - 501M-1B
                    - 1B+
                    - unclassified
        - if:
            type: object
            properties:
              field:
                const: company.fleet_band
            required:
              - field
          then:
            type: object
            properties:
              values:
                type: array
                items:
                  type: string
                  enum:
                    - 1-2
                    - 3-5
                    - 6-9
                    - 10-19
                    - 20-49
                    - 50-99
                    - 100+
                    - unclassified
        - if:
            type: object
            properties:
              field:
                const: company.type
            required:
              - field
          then:
            type: object
            properties:
              values:
                type: array
                items:
                  type: string
                  pattern: ^[a-z0-9]+(_[a-z0-9]+)*$
        - if:
            type: object
            properties:
              field:
                const: company.status
            required:
              - field
          then:
            type: object
            properties:
              values:
                type: array
                items:
                  type: string
                  pattern: ^[a-z0-9]+(_[a-z0-9]+)*$
        - if:
            type: object
            properties:
              field:
                const: company.subscriber_class
            required:
              - field
          then:
            type: object
            properties:
              values:
                type: array
                items:
                  type: string
                  enum:
                    - ltd
                    - plc
                    - llp
                    - scottish_partnership
                    - public_body
                    - charity_incorporated
                    - sole_trader
                    - ordinary_partnership
                    - unknown
        - if:
            type: object
            properties:
              field:
                const: company.companies_house_number
            required:
              - field
          then:
            type: object
            properties:
              values:
                type: array
                items:
                  type: string
                  pattern: ^[A-Z0-9]{8}$
        - if:
            type: object
            properties:
              field:
                const: site.region
            required:
              - field
          then:
            type: object
            properties:
              values:
                type: array
                items:
                  type: string
                  enum:
                    - north_east
                    - north_west
                    - yorkshire_and_the_humber
                    - east_midlands
                    - west_midlands
                    - east_of_england
                    - london
                    - south_east
                    - south_west
                    - scotland
                    - wales
                    - northern_ireland
                    - crown_dependencies
        - if:
            type: object
            properties:
              field:
                const: site.county
            required:
              - field
          then:
            type: object
            properties:
              values:
                type: array
                items:
                  type: string
                  minLength: 1
        - if:
            type: object
            properties:
              field:
                const: site.town
            required:
              - field
          then:
            type: object
            properties:
              values:
                type: array
                items:
                  type: string
                  minLength: 1
        - if:
            type: object
            properties:
              field:
                const: site.postcode
            required:
              - field
          then:
            type: object
            properties:
              values:
                type: array
                items:
                  type: string
                  pattern: ^[A-Za-z]{1,2}([0-9][0-9A-Za-z]?( [0-9]([A-Za-z]{2})?| ?[0-9][A-Za-z]{2})?)?$
        - if:
            type: object
            properties:
              field:
                const: contact.title_family
            required:
              - field
          then:
            type: object
            properties:
              values:
                type: array
                items:
                  type: string
                  minLength: 1
        - if:
            type: object
            properties:
              field:
                const: contact.job_function
            required:
              - field
          then:
            type: object
            properties:
              values:
                type: array
                items:
                  type: string
                  minLength: 1
        - if:
            type: object
            properties:
              field:
                const: contact.job_level_rank
            required:
              - field
          then:
            type: object
            properties:
              values:
                type: array
                items:
                  type: integer
                  enum:
                    - 1
                    - 2
                    - 3
                    - 4
                    - 5
                    - 6
                    - 7
                    - 8
                    - 9
                    - 10
                    - 11
                    - 12
                    - 13
                    - 0
        - if:
            type: object
            properties:
              field:
                const: contact.premium
            required:
              - field
          then:
            type: object
            properties:
              values:
                type: array
                items:
                  type: boolean
        - if:
            type: object
            properties:
              field:
                const: email.exists
            required:
              - field
          then:
            type: object
            properties:
              values:
                type: array
                items:
                  type: boolean
        - if:
            type: object
            properties:
              field:
                const: email.type
            required:
              - field
          then:
            type: object
            properties:
              values:
                type: array
                items:
                  type: string
                  enum:
                    - named
                    - generic_local_part
                    - generic_domain
        - if:
            type: object
            properties:
              field:
                const: phone.exists
            required:
              - field
          then:
            type: object
            properties:
              values:
                type: array
                items:
                  type: boolean
        - if:
            type: object
            properties:
              field:
                const: phone.tps_ctps_status
            required:
              - field
          then:
            type: object
            properties:
              values:
                type: array
                items:
                  type: string
                  enum:
                    - clear
                    - tps_registered
                    - ctps_registered
                    - tps_or_ctps_registered
                    - not_screened
    InListFilter:
      type: object
      properties:
        field:
          type: string
          enum:
            - company.companies_house_number
            - contact.title
            - email.address_sha256
            - email.domain_sha256
            - phone.e164_sha256
        op:
          type: string
          const: in_list
        list_id:
          type: string
          pattern: ^sup_[0-7][0-9A-HJKMNP-TV-Z]{25}$
          description: "Format: a suppression list id: sup_ followed by a 26 character ULID in upper case Crockford base32, such as sup_01JB4H7Q2V7XN3M9R6T1Y5W8ZA."
          examples:
            - sup_01JB4H7Q2V7XN3M9R6T1Y5W8ZA
      required:
        - field
        - op
        - list_id
      additionalProperties: false
      description: The record's hashed email, domain, phone, company number or title term is on a suppression list. Usually nested under not; each list reports the records it removed.
      examples:
        - field: email.address_sha256
          op: in_list
          list_id: sup_01JB4H7Q2V7XN3M9R6T1Y5W8ZA
    InsufficientCreditsProblem:
      type: object
      properties:
        type:
          type: string
          format: uri
          description: A URI under https://emailmovers.com/mydatahub/docs/errors/ naming the problem type.
        title:
          type: string
          description: A short summary of the problem type.
        status:
          type: integer
          minimum: 400
          maximum: 599
        detail:
          type: string
          description: What happened on this request, in plain English.
        instance:
          description: The request path or a request id this problem belongs to.
          type: string
        code:
          type: string
          const: insufficient-credits
        next_step:
          type: string
          description: What to do next, in plain English.
        did_you_mean:
          description: The closest valid values, best first.
          type: array
          items:
            type: string
        errors:
          description: "For validation-failed: every problem found, each with a JSON pointer."
          type: array
          items:
            $ref: "#/components/schemas/ProblemError"
        required_scope:
          description: "For insufficient-scope: the scope to request, such as orders:write."
          type: string
        retry_after_seconds:
          description: "For rate-limited and service-unavailable: the same value as the Retry-After header."
          type: integer
          minimum: 0
        quote_id:
          description: The machine quote to retry after topping up; absent for an enrichment.
          type: string
          pattern: ^qte_[0-7][0-9A-HJKMNP-TV-Z]{25}$
          examples:
            - qte_01JB4M2R6T1Y5W8ZAK8Q2V7XN3
        quote:
          description: The machine quote the balance does not cover; absent for an enrichment.
          $ref: "#/components/schemas/MachineQuote"
        credits_required:
          type: number
          minimum: 0
          description: "Credits, to one decimal place: whole at 1 credit a record, fractional only where a rule is (0.1 company-only, 1.8 outright, both proposed)."
        credits_available:
          type: number
          minimum: 0
          description: "Credits, to one decimal place: whole at 1 credit a record, fractional only where a rule is (0.1 company-only, 1.8 outright, both proposed)."
        top_up_url:
          type: string
          format: uri
          description: Stripe Checkout on mydatahub.emailmovers.com. Money never moves inside an agent host.
      required:
        - type
        - title
        - status
        - detail
        - code
        - next_step
        - credits_required
        - credits_available
        - top_up_url
      description: "402: the balance does not cover the machine quote (or the enrichment), which is carried here with a Stripe Checkout top_up_url. Nothing was charged."
    JurisdictionExclusion:
      type: object
      properties:
        jurisdiction:
          type: string
          description: An ISO 3166 code, such as DE, CA or US-CA.
        removed:
          type: integer
          minimum: 0
      required:
        - jurisdiction
        - removed
      description: Records one policy removed.
    LevelCount:
      type: object
      properties:
        level:
          type: string
        contacts:
          $ref: "#/components/schemas/CellCount"
      required:
        - level
        - contacts
      description: Contacts at one job level.
    LiaDocument:
      type: object
      properties:
        ref:
          type: string
        version:
          type: string
        published_at:
          type: string
          format: date
        url:
          type: string
          format: uri
        lawful_basis:
          type: string
          enum:
            - legitimate_interests_art6_1_f
        summary:
          type: string
        versions:
          type: array
          items:
            type: object
            properties:
              ref:
                type: string
              published_at:
                type: string
                format: date
              url:
                type: string
                format: uri
            required:
              - ref
              - published_at
              - url
      required:
        - ref
        - version
        - published_at
        - url
        - lawful_basis
        - summary
        - versions
      description: The published legitimate interests assessment with its version history.
    Licence:
      type: object
      properties:
        type:
          type: string
          enum:
            - multi_use_12m
            - outright
          description: multi_use_12m (12 month multi-use, every plan) or outright (1.8 credits a record, proposed).
        reuse_until:
          anyOf:
            - type: string
              format: date
            - type: "null"
          description: Order date plus 12 months; null for outright.
        resale_prohibited:
          type: boolean
        permitted_channels:
          type: array
          items:
            type: string
            enum:
              - email
              - telephone
              - post
          description: email, telephone and post on standard terms.
        warranty_url:
          type: string
          format: uri
        terms_version:
          type: string
          description: The version of the Data Licence and Sharing Agreement the licence is under, such as 1.0.
        terms_url:
          description: Where that version of the Data Licence and Sharing Agreement is published.
          type: string
          format: uri
      required:
        - type
        - reuse_until
        - resale_prohibited
        - permitted_channels
        - warranty_url
        - terms_version
      description: Licence terms as data, on every quote, order, records page and receipt. Records stay usable, pushable and re-downloadable free until reuse_until.
    LicenceTerms:
      type: object
      properties:
        licence_type:
          type: string
          enum:
            - multi_use_12m
            - outright
          description: multi_use_12m (12 month multi-use, every plan) or outright (1.8 credits a record, proposed).
        status:
          type: string
          enum:
            - confirmed
            - proposed
        credits_per_record:
          type: number
          exclusiveMinimum: 0
        permitted_channels:
          type: array
          items:
            type: string
            enum:
              - email
              - telephone
              - post
        reuse_months:
          anyOf:
            - type: integer
              exclusiveMinimum: 0
            - type: "null"
          description: 12 for multi_use_12m; null for outright.
        resale_prohibited:
          type: boolean
        warranty_url:
          type: string
          format: uri
        terms_version:
          type: string
      required:
        - licence_type
        - status
        - credits_per_record
        - permitted_channels
        - reuse_months
        - resale_prohibited
        - warranty_url
        - terms_version
      description: Licence terms for a plan, as in licence.json.
    LocationCandidate:
      type: object
      properties:
        kind:
          type: string
          enum:
            - region
            - county
            - town
            - local_authority
            - postcode_area
            - postcode_district
            - postcode_sector
            - postcode_unit
            - geography_set
            - radius
        code:
          type: string
          description: The filter code, or the ONS code for a centre.
        label:
          type: string
        contacts:
          $ref: "#/components/schemas/CellCount"
        centre:
          anyOf:
            - type: object
              properties:
                lat:
                  type: number
                lon:
                  type: number
              required:
                - lat
                - lon
            - type: "null"
        filter:
          description: A ready filter for this candidate.
          $ref: "#/components/schemas/Filter"
          discriminator:
            propertyName: op
            mapping:
              in: "#/components/schemas/InFilter"
              between: "#/components/schemas/BetweenFilter"
              gte: "#/components/schemas/GteFilter"
              lte: "#/components/schemas/LteFilter"
              matches: "#/components/schemas/MatchesFilter"
              within_radius: "#/components/schemas/WithinRadiusFilter"
              in_list: "#/components/schemas/InListFilter"
              since: "#/components/schemas/SinceFilter"
      required:
        - kind
        - code
        - label
        - contacts
        - centre
        - filter
      description: One reading of the text.
    LocationRequest:
      type: object
      properties:
        text:
          type: string
          minLength: 1
          maxLength: 200
          description: Free text such as "20 miles of Leeds", "Yorkshire" or "SW1".
        radius_miles:
          type: number
          minimum: 1
          maximum: 100
        dataset:
          type: string
          enum:
            - uk_contacts
            - ie_contacts
            - global_contacts
          description: The dataset an audience draws from. uk_contacts is live; ie_contacts and global_contacts are listed but not yet served.
      required:
        - text
      additionalProperties: false
      description: A place to resolve into geography filters.
    LocationResolution:
      type: object
      properties:
        text:
          type: string
        ambiguous:
          type: boolean
          description: True when more than one candidate fits; ask the person which they meant.
        question:
          type:
            - string
            - "null"
        candidates:
          type: array
          items:
            $ref: "#/components/schemas/LocationCandidate"
        disclosure:
          $ref: "#/components/schemas/Disclosure"
      required:
        - text
        - ambiguous
        - question
        - candidates
        - disclosure
      description: Candidates for a place. Ambiguous names come back as candidates, never a silent choice.
    LteFilter:
      type: object
      properties:
        field:
          type: string
          enum:
            - company.employee_band
            - company.turnover_band
            - company.fleet_band
            - contact.job_level_rank
            - contact.company_contact_rank
            - email.deliverability_score
        op:
          type: string
          const: lte
        value:
          anyOf:
            - type: string
              minLength: 1
              maxLength: 200
            - type: number
          description: A band code or a number from the field's order.
      required:
        - field
        - op
        - value
      additionalProperties: false
      description: The field is at or before the value in its order. Rank 1 is the most senior, so Director and above is contact.job_level_rank lte 6.
      examples:
        - field: contact.job_level_rank
          op: lte
          value: 6
      allOf:
        - if:
            type: object
            properties:
              field:
                const: company.employee_band
            required:
              - field
          then:
            type: object
            properties:
              value:
                type: string
                enum:
                  - 1-5
                  - 6-9
                  - 10-19
                  - 20-49
                  - 50-99
                  - 100-199
                  - 200-349
                  - 350-499
                  - 500-999
                  - 1000-1499
                  - 1500-4999
                  - 5000-9999
                  - 10000+
        - if:
            type: object
            properties:
              field:
                const: company.turnover_band
            required:
              - field
          then:
            type: object
            properties:
              value:
                type: string
                enum:
                  - 1-100k
                  - 101k-200k
                  - 201k-500k
                  - 501k-750k
                  - 751k-1M
                  - 1.1M-2M
                  - 2.1M-5M
                  - 5.1M-7.5M
                  - 7.6M-10M
                  - 11M-20M
                  - 21M-50M
                  - 51M-75M
                  - 76M-100M
                  - 100M-250M
                  - 251M-500M
                  - 501M-1B
                  - 1B+
        - if:
            type: object
            properties:
              field:
                const: company.fleet_band
            required:
              - field
          then:
            type: object
            properties:
              value:
                type: string
                enum:
                  - 1-2
                  - 3-5
                  - 6-9
                  - 10-19
                  - 20-49
                  - 50-99
                  - 100+
        - if:
            type: object
            properties:
              field:
                const: contact.job_level_rank
            required:
              - field
          then:
            type: object
            properties:
              value:
                type: integer
                enum:
                  - 1
                  - 2
                  - 3
                  - 4
                  - 5
                  - 6
                  - 7
                  - 8
                  - 9
                  - 10
                  - 11
                  - 12
                  - 13
        - if:
            type: object
            properties:
              field:
                const: contact.company_contact_rank
            required:
              - field
          then:
            type: object
            properties:
              value:
                type: integer
                minimum: 1
                maximum: 1000
        - if:
            type: object
            properties:
              field:
                const: email.deliverability_score
            required:
              - field
          then:
            type: object
            properties:
              value:
                type: integer
                minimum: 0
                maximum: 100
    MachineQuote:
      type: object
      properties:
        id:
          type: string
          pattern: ^qte_[0-7][0-9A-HJKMNP-TV-Z]{25}$
          description: "Format: a quote id: qte_ followed by a 26 character ULID in upper case Crockford base32, such as qte_01JB4M2R6T1Y5W8ZAK8Q2V7XN3."
          examples:
            - qte_01JB4M2R6T1Y5W8ZAK8Q2V7XN3
        kind:
          type: string
          const: machine
        organisation_id:
          anyOf:
            - type: string
              pattern: ^org_[0-7][0-9A-HJKMNP-TV-Z]{25}$
              description: "Format: an organisation id: org_ followed by a 26 character ULID in upper case Crockford base32, such as org_01JA0Q2V7XN3M9R6T1Y5W8ZAK8."
              examples:
                - org_01JA0Q2V7XN3M9R6T1Y5W8ZAK8
            - type: "null"
          description: 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:
          type: string
          pattern: ^cnt_[A-Z2-7]{20}$
          description: The count the quote was priced from.
          examples:
            - cnt_7K2PQ4MAXN3R6TBY5WDZ
        dataset:
          type: string
          enum:
            - uk_contacts
            - ie_contacts
            - global_contacts
          description: The dataset an audience draws from. uk_contacts is live; ie_contacts and global_contacts are listed but not yet served.
        dataset_version:
          type: string
          pattern: ^[0-9]{4}-W(0[1-9]|[1-4][0-9]|5[0-3])$
          description: The immutable weekly build a count is bound to, such as 2026-W39. The last 52 stay queryable.
          examples:
            - 2026-W39
        field_set:
          type: string
          pattern: ^[a-z0-9]+(_[a-z0-9]+)*$
        records:
          type: integer
          minimum: 0
          description: Records after overlap and suppression; what you would receive.
        removed:
          type: object
          properties:
            already_held:
              type: integer
              minimum: 0
            suppressed:
              type: integer
              minimum: 0
          required:
            - already_held
            - suppressed
          description: Records left out and never charged.
        credits_per_record:
          type: number
          minimum: 0
          description: 1 for a complete UK record; 1.8 outright (proposed).
        credits_required:
          type: number
          minimum: 0
          description: records times credits_per_record (creditsFor).
        credits_available:
          anyOf:
            - type: number
              minimum: 0
              description: "Credits, to one decimal place: whole at 1 credit a record, fractional only where a rule is (0.1 company-only, 1.8 outright, both proposed)."
            - type: "null"
          description: Your balance; null when signed out.
        balance_after:
          description: credits_available less credits_required; negative means a top-up is needed; null when signed out.
          type:
            - number
            - "null"
        gbp_ex_vat:
          type: number
          minimum: 0
          description: 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:
          $ref: "#/components/schemas/Purchase"
        licence:
          $ref: "#/components/schemas/Licence"
        warranty:
          $ref: "#/components/schemas/Warranty"
        created_at:
          type: string
          format: date-time
        expires_at:
          type: string
          format: date-time
          description: 24 hours after creation.
        blockers:
          type: array
          items:
            $ref: "#/components/schemas/QuoteBlocker"
        signature:
          $ref: "#/components/schemas/QuoteSignature"
      required:
        - id
        - kind
        - organisation_id
        - count_id
        - dataset_version
        - records
        - removed
        - credits_per_record
        - credits_required
        - credits_available
        - balance_after
        - gbp_ex_vat
        - purchase
        - licence
        - warranty
        - expires_at
        - blockers
        - signature
      description: "A machine quote: a signed price, valid 24 hours, bound to a dataset version and an organisation. create_order needs its id plus a human confirmation or a pre-authorised agent key; a confirmed flag set by a model is never consent."
    MatchesFilter:
      type: object
      properties:
        field:
          type: string
          enum:
            - contact.title
        op:
          type: string
          const: matches
        terms:
          minItems: 1
          maxItems: 500
          type: array
          items:
            type: string
            minLength: 1
            maxLength: 200
        mode:
          type: string
          enum:
            - contains
            - starts_with
            - exact
          description: contains (the default) matches titles containing a term, starts_with matches titles beginning with it, exact takes verbatim titles with no expansion.
        expand:
          description: "Expansion sets to apply, each once: plural_singular, department_synonyms, ampersand_and, abbreviations, permutations. Left out, the first four apply; an empty list applies none. Not used with mode exact."
          uniqueItems: true
          maxItems: 5
          type: array
          items:
            type: string
            enum:
              - plural_singular
              - department_synonyms
              - ampersand_and
              - abbreviations
              - permutations
        exclude_terms:
          description: Expanded terms removed by hand. Kept in the definition so the count stays reproducible when the title dictionary changes.
          maxItems: 500
          type: array
          items:
            type: string
            minLength: 1
            maxLength: 200
      required:
        - field
        - op
        - terms
      additionalProperties: false
      description: Job title keyword match. mode contains (the default) or starts_with expands each term with the expansion sets; mode exact matches verbatim titles. Every expanded term is returned with the count.
      examples:
        - field: contact.title
          op: matches
          terms:
            - marketing manager
          mode: contains
          expand:
            - plural_singular
            - department_synonyms
            - ampersand_and
            - abbreviations
      allOf:
        - if:
            type: object
            properties:
              mode:
                const: exact
            required:
              - mode
          then:
            type: object
            properties:
              expand:
                type: array
                maxItems: 0
              exclude_terms:
                type: array
                maxItems: 0
    Meter:
      type: object
      properties:
        action:
          type: string
          pattern: ^[a-z0-9]+(_[a-z0-9]+)*$
          description: What is metered, such as record or enrich_contact.
        unit:
          type: string
          enum:
            - record
            - match
            - call
        credits:
          type: number
          minimum: 0
          description: Credits per unit.
        status:
          type: string
          enum:
            - confirmed
            - proposed
            - to_confirm
          description: confirmed is in force; proposed awaits a decision; to_confirm is a figure still being checked.
      required:
        - action
        - unit
        - credits
        - status
      description: One metered action and its price in credits.
    NotGroup:
      type: object
      properties:
        not:
          $ref: "#/components/schemas/WhereNode"
      required:
        - not
      additionalProperties: false
      description: "Negation: the single node inside must not match."
    NoticeVersion:
      type: object
      properties:
        version:
          type: string
        published_at:
          type: string
          format: date
        url:
          type: string
          format: uri
      required:
        - version
        - published_at
        - url
      description: One dated version of the notice.
    Objection:
      type: object
      properties:
        suppression_id:
          type: string
          pattern: ^sup_[0-7][0-9A-HJKMNP-TV-Z]{25}$
          description: The list the record now sits on.
          examples:
            - sup_01JB4H7Q2V7XN3M9R6T1Y5W8ZA
        record_id:
          anyOf:
            - type: string
              pattern: ^contact_[0-7][0-9A-HJKMNP-TV-Z]{25}$
              description: "Format: a contact id: contact_ followed by a 26 character ULID in upper case Crockford base32, such as contact_01JA9X2V7XN3M9R6T1Y5W8ZAK8."
              examples:
                - contact_01JA9X2V7XN3M9R6T1Y5W8ZAK8
            - type: "null"
        scope:
          type: string
          enum:
            - licensee
            - emailmovers
        status:
          type: string
          enum:
            - suppressed
            - pending
          description: suppressed now, or pending until the database-wide overlay applies.
        suppressed_at:
          anyOf:
            - type: string
              format: date-time
            - type: "null"
        effective_by:
          type: string
          format: date-time
      required:
        - suppression_id
        - record_id
        - scope
        - status
        - suppressed_at
        - effective_by
      description: The suppression an objection created. Database-wide objections reach every licensee holding the record through the suppression.created webhook.
    ObjectionRequest:
      type: object
      properties:
        record_id:
          type: string
          pattern: ^contact_[0-7][0-9A-HJKMNP-TV-Z]{25}$
          description: "Format: a contact id: contact_ followed by a 26 character ULID in upper case Crockford base32, such as contact_01JA9X2V7XN3M9R6T1Y5W8ZAK8."
          examples:
            - contact_01JA9X2V7XN3M9R6T1Y5W8ZAK8
        email_sha256:
          description: SHA-256 of the trimmed, lower cased address, when the record id is not known.
          type: string
          pattern: ^[0-9a-f]{64}$
        channel:
          type: string
          enum:
            - email
            - telephone
            - post
            - web_form
            - other
        received_at:
          type: string
          format: date-time
          description: RFC 3339 date and time, with Z or an offset such as +01:00.
        scope:
          type: string
          enum:
            - licensee
            - emailmovers
          description: licensee suppresses the record for your organisation at once; emailmovers suppresses it database-wide within one working day.
        note:
          type: string
          maxLength: 2000
      required:
        - channel
        - received_at
        - scope
      additionalProperties: false
      description: "An objection to direct marketing, to record and propagate: exactly one of record_id or email_sha256."
      oneOf:
        - type: object
          properties:
            record_id: true
          required:
            - record_id
        - type: object
          properties:
            email_sha256: true
          required:
            - email_sha256
    OpenApiDocument:
      type: object
      propertyNames:
        type: string
      additionalProperties: {}
      description: This OpenAPI 3.1 document, with x-cost-credits on every operation.
    OperatorInfo:
      type: object
      properties:
        op:
          type: string
          enum:
            - in
            - between
            - gte
            - lte
            - matches
            - within_radius
            - in_list
            - since
            - all
            - any
            - not
        applies_to:
          type: string
        description:
          type: string
      required:
        - op
        - applies_to
        - description
      description: An operator of the audience definition.
    Order:
      type: object
      properties:
        id:
          type: string
          pattern: ^ord_[0-7][0-9A-HJKMNP-TV-Z]{25}$
          description: "Format: an order id: ord_ followed by a 26 character ULID in upper case Crockford base32, such as ord_01JB4N9S3W2X6Y7Z8A1B2C3D4E."
          examples:
            - ord_01JB4N9S3W2X6Y7Z8A1B2C3D4E
        status:
          type: string
          enum:
            - queued
            - building
            - verifying
            - ready
            - failed
            - cancelled
          description: queued, building, verifying, ready; failed if processing failed; cancelled (only while queued).
        name:
          type: string
        quote_id:
          type: string
          pattern: ^qte_[0-7][0-9A-HJKMNP-TV-Z]{25}$
          description: The machine quote the order settled.
          examples:
            - qte_01JB4M2R6T1Y5W8ZAK8Q2V7XN3
        count_id:
          type: string
          pattern: ^cnt_[A-Z2-7]{20}$
          description: The count the machine quote was priced from.
          examples:
            - cnt_7K2PQ4MAXN3R6TBY5WDZ
        dataset_version:
          type: string
          pattern: ^[0-9]{4}-W(0[1-9]|[1-4][0-9]|5[0-3])$
          description: The immutable weekly build a count is bound to, such as 2026-W39. The last 52 stay queryable.
          examples:
            - 2026-W39
        field_set:
          type: string
          pattern: ^[a-z0-9]+(_[a-z0-9]+)*$
        delivery:
          type: array
          items:
            type: string
            enum:
              - api
              - csv
              - google_sheets
        records:
          type: integer
          minimum: 0
        credits_charged:
          type: number
          minimum: 0
          description: "Credits, to one decimal place: whole at 1 credit a record, fractional only where a rule is (0.1 company-only, 1.8 outright, both proposed)."
        credits_remaining:
          type: number
          minimum: 0
          description: The balance right after this order was charged.
        cost_gbp:
          type: number
          minimum: 0
          description: The charge in GBP excluding VAT at your plan's rate.
        licence_expires_at:
          anyOf:
            - type: string
              format: date
            - type: "null"
          description: When the licence ends; null for outright ownership.
        licence:
          $ref: "#/components/schemas/Licence"
        confirmed_by:
          $ref: "#/components/schemas/ConfirmedBy"
        verification:
          description: The real-time check at delivery, such as 4,102 checked, 4,071 passed.
          type: object
          properties:
            checked:
              type: integer
              minimum: 0
            passed:
              type: integer
              minimum: 0
            replaced:
              type: integer
              minimum: 0
          required:
            - checked
            - passed
            - replaced
        records_url:
          type: string
          pattern: ^\/v1\/[A-Za-z0-9_\-./]+$
          format: uri-reference
          description: A path on https://mydatahub.emailmovers.com starting /v1/. Resolve it against the origin (RFC 3986), not by appending it to the server URL.
        download_url:
          description: 302 to a single-use signed URL that lives 5 minutes.
          type: string
          pattern: ^\/v1\/[A-Za-z0-9_\-./]+$
          format: uri-reference
        receipt_url:
          type: string
          pattern: ^\/v1\/[A-Za-z0-9_\-./]+$
          format: uri-reference
          description: A path on https://mydatahub.emailmovers.com starting /v1/. Resolve it against the origin (RFC 3986), not by appending it to the server URL.
        validation_report:
          description: Present once the order is ready.
          $ref: "#/components/schemas/ValidationReportLinks"
        buyer_kit:
          $ref: "#/components/schemas/BuyerKit"
        created_at:
          type: string
          format: date-time
        ready_at:
          anyOf:
            - type: string
              format: date-time
            - type: "null"
        failure:
          type:
            - string
            - "null"
      required:
        - id
        - status
        - quote_id
        - count_id
        - records
        - credits_charged
        - credits_remaining
        - cost_gbp
        - licence_expires_at
        - licence
        - confirmed_by
        - records_url
      description: "The purchase and its delivery: status, records, charges, the licence, links to records, download, the validation report and the signed compliance receipt."
    OrderFailedEvent:
      type: object
      properties:
        id:
          type: string
          pattern: ^evt_[0-7][0-9A-HJKMNP-TV-Z]{25}$
          description: Also sent as the webhook-id header; your idempotency key.
          examples:
            - evt_01JB4NB7T1Y5W8ZAK8Q2V7XN3M
        type:
          type: string
          const: order.failed
        timestamp:
          type: string
          format: date-time
        api_version:
          type: string
          pattern: ^[0-9]{4}-(0[1-9]|1[0-2])-(0[1-9]|[12][0-9]|3[01])$
          description: A dated MyDataHub-Version, such as 2027-01-15.
        data:
          type: object
          properties:
            order_id:
              type: string
              pattern: ^ord_[0-7][0-9A-HJKMNP-TV-Z]{25}$
              description: "Format: an order id: ord_ followed by a 26 character ULID in upper case Crockford base32, such as ord_01JB4N9S3W2X6Y7Z8A1B2C3D4E."
              examples:
                - ord_01JB4N9S3W2X6Y7Z8A1B2C3D4E
            reason:
              type: string
            credits_refunded:
              type: number
              minimum: 0
              description: "Credits, to one decimal place: whole at 1 credit a record, fractional only where a rule is (0.1 company-only, 1.8 outright, both proposed)."
          required:
            - order_id
            - reason
            - credits_refunded
      required:
        - id
        - type
        - timestamp
        - api_version
        - data
      description: Processing failed; credits are refunded.
    OrderList:
      type: object
      properties:
        data:
          type: array
          items:
            $ref: "#/components/schemas/Order"
        has_more:
          type: boolean
          description: Whether another page follows.
        next_cursor:
          anyOf:
            - type: string
              minLength: 1
              maxLength: 512
              description: An opaque cursor from next_cursor. Pass it back unchanged.
            - type: "null"
          description: Pass as cursor to fetch the next page; null on the last page.
      required:
        - data
        - has_more
        - next_cursor
      description: A page of orders, newest first.
    OrderReadyEvent:
      type: object
      properties:
        id:
          type: string
          pattern: ^evt_[0-7][0-9A-HJKMNP-TV-Z]{25}$
          description: Also sent as the webhook-id header; your idempotency key.
          examples:
            - evt_01JB4NB7T1Y5W8ZAK8Q2V7XN3M
        type:
          type: string
          const: order.ready
        timestamp:
          type: string
          format: date-time
        api_version:
          type: string
          pattern: ^[0-9]{4}-(0[1-9]|1[0-2])-(0[1-9]|[12][0-9]|3[01])$
          description: A dated MyDataHub-Version, such as 2027-01-15.
        data:
          type: object
          properties:
            order_id:
              type: string
              pattern: ^ord_[0-7][0-9A-HJKMNP-TV-Z]{25}$
              description: "Format: an order id: ord_ followed by a 26 character ULID in upper case Crockford base32, such as ord_01JB4N9S3W2X6Y7Z8A1B2C3D4E."
              examples:
                - ord_01JB4N9S3W2X6Y7Z8A1B2C3D4E
            records:
              type: integer
              minimum: 0
            records_url:
              type: string
              pattern: ^\/v1\/[A-Za-z0-9_\-./]+$
              format: uri-reference
              description: A path on https://mydatahub.emailmovers.com starting /v1/. Resolve it against the origin (RFC 3986), not by appending it to the server URL.
            receipt_url:
              type: string
              pattern: ^\/v1\/[A-Za-z0-9_\-./]+$
              format: uri-reference
              description: A path on https://mydatahub.emailmovers.com starting /v1/. Resolve it against the origin (RFC 3986), not by appending it to the server URL.
          required:
            - order_id
            - records
            - records_url
            - receipt_url
      required:
        - id
        - type
        - timestamp
        - api_version
        - data
      description: Records are available.
    OrderRequest:
      type: object
      properties:
        quote_id:
          type: string
          pattern: ^qte_[0-7][0-9A-HJKMNP-TV-Z]{25}$
          description: A live machine quote from quote_order. Without one nothing is charged.
          examples:
            - qte_01JB4M2R6T1Y5W8ZAK8Q2V7XN3
        name:
          type: string
          minLength: 1
          maxLength: 200
        delivery:
          description: api (fetch records), csv (signed download) and google_sheets. Default api.
          minItems: 1
          type: array
          items:
            type: string
            enum:
              - api
              - csv
              - google_sheets
        confirmation_token:
          type: string
          minLength: 16
          maxLength: 2000
          description: The token the approval page on mydatahub.emailmovers.com returns once the signed-in person approves. It is bound to that person (their sub), this spend (the machine quote_id, or the enrichment or push) and an expiry no later than the quote's, and it is redeemable once.
        licence_accepted_version:
          description: "The version of the Data Licence and Sharing Agreement the person accepted for this order: the version in force, the account's agreements.dlsa.version. Required when a person orders in the app; an agent key carries the acceptance made with it, and a confirmation_token the one made on the approval page."
          type: string
          minLength: 1
          maxLength: 100
      required:
        - quote_id
      additionalProperties: false
      description: "Spend credits against a machine quote. A model-set confirmed flag is never consent: the order needs a human confirmation (MCP elicitation or confirmation_token) or a pre-authorised agent key within its limits."
    OtherBadge:
      type: object
      properties:
        badge:
          type: string
          enum:
            - re_checking
            - unverified
            - suppressed
        date:
          anyOf:
            - type: string
              format: date
            - type: "null"
          description: The date behind the badge, if any.
        label:
          type: string
          description: Such as Verified 12 September 2026.
      required:
        - badge
        - date
        - label
      description: Re-checking, unverified or suppressed.
    OtherBucket:
      type: object
      properties:
        cells:
          type: integer
          minimum: 0
          description: How many cells were folded into other.
        contacts:
          $ref: "#/components/schemas/CellCount"
        companies:
          $ref: "#/components/schemas/CellCount"
      required:
        - cells
        - contacts
      description: Every cell beyond the top 50, summed.
    Overlap:
      type: object
      properties:
        connection_id:
          type: string
          pattern: ^conn_[0-7][0-9A-HJKMNP-TV-Z]{25}$
          description: "Format: a CRM connection id: conn_ followed by a 26 character ULID in upper case Crockford base32, such as conn_01JB3Y2X6Y7Z8A1B2C3D4E5F6G."
          examples:
            - conn_01JB3Y2X6Y7Z8A1B2C3D4E5F6G
        count_id:
          type: string
          pattern: ^cnt_[A-Z2-7]{20}$
          description: "Format: a count id: cnt_ followed by 20 base32 characters (A to Z and 2 to 7), such as cnt_7K2PQ4MAXN3R6TBY5WDZ."
          examples:
            - cnt_7K2PQ4MAXN3R6TBY5WDZ
        records:
          type: integer
          minimum: 0
        already_held:
          type: integer
          minimum: 0
          description: Records already in the CRM; never charged.
        new_to_you:
          type: integer
          minimum: 0
        method:
          type: string
          const: salted_sha256_in_memory
        retained:
          type: boolean
          const: false
          description: Nothing from the CRM is kept after the check.
        checked_at:
          type: string
          format: date-time
      required:
        - connection_id
        - count_id
        - records
        - already_held
        - new_to_you
        - method
        - retained
        - checked_at
      description: "The New to you overlap check: salted SHA-256 email matching in memory, nothing retained."
    OverlapRequest:
      type: object
      properties:
        count_id:
          type: string
          pattern: ^cnt_[A-Za-z2-7]{20}$
          description: A count id from POST /v1/counts or count_audience. Either case is accepted; responses use upper case.
          examples:
            - cnt_7K2PQ4MAXN3R6TBY5WDZ
        quote_id:
          type: string
          pattern: ^qte_[0-7][0-9A-HJKMNP-TV-Z]{25}$
          description: "Format: a quote id: qte_ followed by a 26 character ULID in upper case Crockford base32, such as qte_01JB4M2R6T1Y5W8ZAK8Q2V7XN3."
          examples:
            - qte_01JB4M2R6T1Y5W8ZAK8Q2V7XN3
      additionalProperties: false
      description: "The count or the machine quote to compare with the CRM: exactly one of count_id or quote_id."
      oneOf:
        - type: object
          properties:
            count_id: true
          required:
            - count_id
        - type: object
          properties:
            quote_id: true
          required:
            - quote_id
    PaymentReceipt:
      type: object
      properties:
        number:
          type: string
          description: "The receipt number, such as MDH-000042: one sequence for every receipt, with no number skipped."
        issued_at:
          type: string
          format: date-time
        supplier:
          type: object
          properties:
            name:
              description: The company, such as Emailmovers Limited.
              type:
                - string
                - "null"
            company_number:
              type:
                - string
                - "null"
            registered_office:
              type:
                - string
                - "null"
            vat_number:
              type:
                - string
                - "null"
          required:
            - name
            - company_number
            - registered_office
            - vat_number
          description: Who sold the credits, as the server's settings name them when the receipt is written. In production a receipt is never written without them; a development server may leave them null.
        customer:
          type: object
          properties:
            organisation_id:
              type: string
              pattern: ^org_[0-7][0-9A-HJKMNP-TV-Z]{25}$
              description: "Format: an organisation id: org_ followed by a 26 character ULID in upper case Crockford base32, such as org_01JA0Q2V7XN3M9R6T1Y5W8ZAK8."
              examples:
                - org_01JA0Q2V7XN3M9R6T1Y5W8ZAK8
            organisation_name:
              type: string
            business_name:
              description: "The organisation's business name when the receipt was written, which the receipt leads with: confirmed by an owner for an account made by self-serve sign-up, or the name Emailmovers set the account up under. Null while a self-serve account has not confirmed it, and on receipts written before 21 October 2026."
              type:
                - string
                - "null"
            name:
              description: "The name on the billing details Checkout collected: the business name when the buyer gave one."
              type:
                - string
                - "null"
            address:
              anyOf:
                - $ref: "#/components/schemas/BillingAddress"
                - type: "null"
            vat_number:
              description: The buyer's own VAT number, when they gave one at checkout.
              type:
                - string
                - "null"
          required:
            - organisation_id
            - organisation_name
            - business_name
            - name
            - address
            - vat_number
          description: Who bought the credits, as Stripe Checkout collected it.
        description:
          type: string
          description: What was bought, such as 1 credit pack of 40,000.
        credits:
          type: integer
          minimum: 0
        gbp_per_credit:
          type: number
          minimum: 0
          description: "The unit price: one credit, excluding VAT, to six decimal places."
        gbp_ex_vat:
          type: number
          minimum: 0
          description: Pounds sterling excluding VAT, to two decimal places.
        vat_rate_percent:
          type: number
          minimum: 0
          maximum: 100
        vat_gbp:
          type: number
          minimum: 0
          description: Pounds sterling, to two decimal places.
        total_gbp:
          type: number
          minimum: 0
          description: What the card was charged, VAT included.
        paid_by:
          type: string
          const: card
          description: Card, through Stripe Checkout.
        payment_reference:
          type: string
          description: Stripe's reference for the payment.
        refunds:
          type: string
          description: The refund terms the purchase was made under.
        test_mode:
          type: boolean
          description: "A Stripe test mode payment: no money moved."
      required:
        - number
        - issued_at
        - supplier
        - customer
        - description
        - credits
        - gbp_per_credit
        - gbp_ex_vat
        - vat_rate_percent
        - vat_gbp
        - total_gbp
        - paid_by
        - payment_reference
        - refunds
        - test_mode
      description: "The receipt for a paid credit purchase, which serves as the VAT invoice: the supplier and its VAT number, the buyer's name, billing address and VAT number when given, what was bought, the price per credit and in total excluding VAT, the VAT rate and amount, and the total. Written once, when the credits are added."
    PlaceCentre:
      type: object
      properties:
        place:
          type: string
          minLength: 1
          maxLength: 100
          description: A place name, resolved with resolve_location, such as Leeds.
        code:
          description: The ONS geography code of the chosen candidate when resolve_location found more than one, such as E08000035 for the Leeds district.
          type: string
          minLength: 1
          maxLength: 20
      required:
        - place
      additionalProperties: false
      description: A named place. Ambiguous names come back from resolve_location as candidates, never a silent choice.
    Plan:
      type: object
      properties:
        id:
          type: string
          pattern: ^[a-z0-9]+(_[a-z0-9]+)*$
          description: Such as free, pay_as_you_go or plan_12_month.
        name:
          type: string
        kind:
          type: string
          enum:
            - free
            - subscription
            - pack
        status:
          type: string
          enum:
            - confirmed
            - proposed
            - to_confirm
          description: confirmed is in force; proposed awaits a decision; to_confirm is a figure still being checked.
        available_to:
          type: string
          enum:
            - everyone
            - new_customers
            - existing_customers
          description: everyone; new_customers only (the subscription); existing_customers only, who keep their prices (pay as you go and the 12-month plan).
        price_gbp_ex_vat:
          type: number
          minimum: 0
          description: Pounds sterling excluding VAT, to two decimal places.
        billing_period:
          type: string
          enum:
            - once
            - month
            - year
        contract_months:
          description: The contract's length in months, such as 12; absent when there is none.
          anyOf:
            - type: integer
              minimum: 0
            - type: "null"
        annual_price_gbp_ex_vat:
          anyOf:
            - type: number
              minimum: 0
              description: Pounds sterling excluding VAT, to two decimal places.
            - type: "null"
        credits:
          type: integer
          minimum: 0
          description: Credits per billing period, or once.
        per_record_gbp:
          anyOf:
            - type: number
              minimum: 0
              description: Pounds sterling excluding VAT, to two decimal places.
            - type: "null"
          description: Price per record at full use of the allowance; null for the free plan.
        rollover_cap_credits:
          description: Subscription credits roll over while subscribed, up to this many; 0 when each period's credits are used or lost.
          anyOf:
            - type: integer
              minimum: 0
            - type: "null"
        credits_expire:
          type: boolean
        for_whom:
          type: string
        notes:
          type: string
      required:
        - id
        - name
        - kind
        - status
        - available_to
        - price_gbp_ex_vat
        - billing_period
        - credits
        - per_record_gbp
        - credits_expire
        - for_whom
      description: "A plan: free, a rolling subscription or a credit pack. No seats on any plan."
    PointCentre:
      type: object
      properties:
        lat:
          type: number
          minimum: 49
          maximum: 61
        lon:
          type: number
          minimum: -9
          maximum: 2
      required:
        - lat
        - lon
      additionalProperties: false
      description: A latitude and longitude in WGS84 degrees.
    PostcodeCentre:
      type: object
      properties:
        postcode:
          type: string
          pattern: ^[A-Za-z]{1,2}([0-9][0-9A-Za-z]?( [0-9]([A-Za-z]{2})?| ?[0-9][A-Za-z]{2})?)?$
          description: The centroid of a postcode unit, sector, district or area, such as LS1 4AP.
      required:
        - postcode
      additionalProperties: false
      description: A postcode centroid from ONSPD.
    Preview:
      type: object
      properties:
        id:
          type: string
          pattern: ^prv_[0-7][0-9A-HJKMNP-TV-Z]{25}$
          description: "Format: a preview id: prv_ followed by a 26 character ULID in upper case Crockford base32, such as prv_01JB4H3M7Q2V7XN3M9R6T1Y5W8."
          examples:
            - prv_01JB4H3M7Q2V7XN3M9R6T1Y5W8
        count_id:
          type: string
          pattern: ^cnt_[A-Z2-7]{20}$
          description: "Format: a count id: cnt_ followed by 20 base32 characters (A to Z and 2 to 7), such as cnt_7K2PQ4MAXN3R6TBY5WDZ."
          examples:
            - cnt_7K2PQ4MAXN3R6TBY5WDZ
        dataset_version:
          type: string
          pattern: ^[0-9]{4}-W(0[1-9]|[1-4][0-9]|5[0-3])$
          description: The immutable weekly build a count is bound to, such as 2026-W39. The last 52 stay queryable.
          examples:
            - 2026-W39
        seed:
          type: string
          description: The seed the rows came from; the same audience gives the same seed.
        count:
          type: integer
          minimum: 0
          description: The count the rows were drawn from, rounded as disclosure says.
        disclosure:
          $ref: "#/components/schemas/Disclosure"
        rows:
          maxItems: 25
          type: array
          items:
            $ref: "#/components/schemas/PreviewRow"
        withheld:
          anyOf:
            - type: object
              properties:
                rows:
                  type: integer
                  minimum: 0
                reason:
                  type: string
              required:
                - rows
                - reason
            - type: "null"
          description: "Rows held back: all of them when the count is under 25, or rows whose title fewer than 10 people hold."
        masked:
          type: boolean
          const: true
        watermark:
          description: The account id watermarked into sample CSVs; null when signed out.
          type:
            - string
            - "null"
        created_at:
          type: string
          format: date-time
      required:
        - id
        - count_id
        - dataset_version
        - seed
        - count
        - disclosure
        - rows
        - withheld
        - masked
        - watermark
        - created_at
      description: A free, masked sample of up to 25 rows. Repeating a sample returns the same rows.
    PreviewRequest:
      type: object
      properties:
        audience:
          $ref: "#/components/schemas/AudienceRef"
        count_id:
          description: A count id instead of an audience.
          type: string
          pattern: ^cnt_[A-Za-z2-7]{20}$
          examples:
            - cnt_7K2PQ4MAXN3R6TBY5WDZ
        limit:
          description: Rows to return, 1 to 25. Default 10.
          default: 10
          type: integer
          minimum: 1
          maximum: 25
        shuffle:
          description: Draw a new seed. Counts against your rate budget and is logged. Default false, so a repeat returns the same rows.
          default: false
          type: boolean
      additionalProperties: false
      description: "The audience or count to sample: exactly one of audience or count_id."
      oneOf:
        - type: object
          properties:
            audience: true
          required:
            - audience
        - type: object
          properties:
            count_id: true
          required:
            - count_id
    PreviewRow:
      type: object
      properties:
        id:
          type: string
          pattern: ^prv_[0-7][0-9A-HJKMNP-TV-Z]{25}$
          description: A session-scoped id standing in for the record. It never reveals contact_ ids.
          examples:
            - prv_01JB4H3M7Q2V7XN3M9R6T1Y5W8
        title:
          type: string
        job_level:
          description: The level, such as Director; null when the level is not known.
          type:
            - string
            - "null"
        job_function:
          type:
            - string
            - "null"
        company:
          type: object
          properties:
            name:
              type: string
            companies_house_number:
              type:
                - string
                - "null"
            status:
              type: string
            employee_band:
              type:
                - string
                - "null"
            sic_2007:
              type: array
              items:
                type: string
          required:
            - name
            - companies_house_number
            - status
            - employee_band
            - sic_2007
        site:
          type: object
          properties:
            town:
              type:
                - string
                - "null"
            postcode_district:
              type:
                - string
                - "null"
            region:
              type: string
          required:
            - town
            - postcode_district
            - region
        email_masked:
          description: First character of the local part plus the top level domain, such as j••••@••••.co.uk.
          type:
            - string
            - "null"
        email_type:
          anyOf:
            - type: string
              enum:
                - named
                - generic_local_part
                - generic_domain
            - type: "null"
        phone_masked:
          description: To the area code, such as 0113 ••• •••1.
          type:
            - string
            - "null"
        tps_ctps_status:
          anyOf:
            - type: string
              enum:
                - clear
                - tps_registered
                - ctps_registered
                - tps_or_ctps_registered
                - not_screened
            - type: "null"
        badges:
          type: array
          items:
            $ref: "#/components/schemas/VerificationBadge"
        why_it_matches:
          type: array
          items:
            $ref: "#/components/schemas/WhyItMatches"
        provenance:
          $ref: "#/components/schemas/ProvenanceCard"
        compliance:
          $ref: "#/components/schemas/ComplianceObject"
      required:
        - id
        - title
        - job_level
        - job_function
        - company
        - site
        - email_masked
        - email_type
        - phone_masked
        - tps_ctps_status
        - badges
        - why_it_matches
        - provenance
        - compliance
      description: One masked sample record.
    PriceBand:
      type: object
      properties:
        from_records:
          type: integer
          minimum: 0
        to_records:
          anyOf:
            - type: integer
              minimum: 0
            - type: "null"
          description: null for the last band.
        gbp_per_record:
          type: number
          minimum: 0
          description: Pounds sterling excluding VAT, to two decimal places.
      required:
        - from_records
        - to_records
        - gbp_per_record
      description: One band of the per-contact scale.
    PriceEstimate:
      type: object
      properties:
        credits:
          type: number
          minimum: 0
          description: "Credits, to one decimal place: whole at 1 credit a record, fractional only where a rule is (0.1 company-only, 1.8 outright, both proposed)."
        gbp_ex_vat:
          type: object
          propertyNames:
            type: string
            pattern: ^[a-z0-9]+(_[a-z0-9]+)*$
          additionalProperties:
            type: number
            minimum: 0
            description: Pounds sterling excluding VAT, to two decimal places.
          description: "The price in GBP excluding VAT: per_contact_scale (every credit at the scale) and cheapest (the cheapest real purchase, at the scale or with credit packs)."
      required:
        - credits
        - gbp_ex_vat
      description: What ordering the whole count would cost. An estimate; quote_order gives the machine quote.
    PriceScale:
      type: object
      properties:
        id:
          type: string
          const: per_contact_scale
        name:
          type: string
        status:
          type: string
          enum:
            - confirmed
            - proposed
            - to_confirm
          description: confirmed is in force; proposed awaits a decision; to_confirm is a figure still being checked.
        available_to:
          type: string
          enum:
            - everyone
            - new_customers
            - existing_customers
          description: everyone; new_customers only (the subscription); existing_customers only, who keep their prices (pay as you go and the 12-month plan).
        minimum_order_records:
          type: integer
          minimum: 0
        rule:
          type: string
          description: How a purchase is priced, in words.
        bands:
          minItems: 1
          type: array
          items:
            $ref: "#/components/schemas/PriceBand"
      required:
        - id
        - name
        - status
        - available_to
        - minimum_order_records
        - rule
        - bands
      description: "The per-contact scale: 1 credit buys 1 record at the rate of the band the purchase falls in. A quote's purchase options compare it with the credit pack and lead with the cheapest."
    Pricing:
      type: object
      properties:
        version:
          type: string
        valid_from:
          type: string
          format: date
        currency:
          type: string
          const: GBP
        vat_treatment:
          type: string
        credit_definition:
          type: string
        price_scale:
          $ref: "#/components/schemas/PriceScale"
        plans:
          type: array
          items:
            $ref: "#/components/schemas/Plan"
        credit_packs:
          type: array
          items:
            $ref: "#/components/schemas/CreditPack"
        credit_rules:
          type: array
          items:
            $ref: "#/components/schemas/CreditRule"
        licence:
          type: array
          items:
            $ref: "#/components/schemas/LicenceTerms"
        guarantee:
          type: object
          properties:
            current:
              $ref: "#/components/schemas/Warranty"
            proposed:
              anyOf:
                - $ref: "#/components/schemas/Warranty"
                - type: "null"
            refunds:
              type: array
              items:
                type: string
          required:
            - current
            - proposed
            - refunds
        trial:
          type: object
          properties:
            credits:
              type: integer
              minimum: 0
            card_required:
              type: boolean
            expires:
              type: boolean
          required:
            - credits
            - card_required
            - expires
        promises:
          type: array
          items:
            $ref: "#/components/schemas/PricingPromise"
        meters:
          type: array
          items:
            $ref: "#/components/schemas/Meter"
          description: What agents and code are metered on, in credits, the same on every surface.
        action_path:
          type: array
          items:
            $ref: "#/components/schemas/ActionPathStep"
          description: "How to buy: quote_order for a machine quote, create_order with a confirmation, and the top-up page when the balance is short."
      required:
        - version
        - valid_from
        - currency
        - vat_treatment
        - credit_definition
        - price_scale
        - plans
        - credit_packs
        - credit_rules
        - licence
        - guarantee
        - trial
        - promises
        - meters
        - action_path
      description: The per-contact scale, plans, credit packs, credit rules, licence terms, the guarantee and the promises, in GBP excluding VAT. The same content as https://mydatahub.emailmovers.com/pricing.json.
    PricingPromise:
      type: object
      properties:
        key:
          type: string
          pattern: ^[a-z0-9]+(_[a-z0-9]+)*$
        statement:
          type: string
        in_practice:
          type: string
      required:
        - key
        - statement
        - in_practice
      description: One of the seven promises on the pricing page.
    Problem:
      type: object
      properties:
        type:
          type: string
          format: uri
          description: A URI under https://emailmovers.com/mydatahub/docs/errors/ naming the problem type.
        title:
          type: string
          description: A short summary of the problem type.
        status:
          type: integer
          minimum: 400
          maximum: 599
        detail:
          type: string
          description: What happened on this request, in plain English.
        instance:
          description: The request path or a request id this problem belongs to.
          type: string
        code:
          type: string
          enum:
            - invalid-request
            - idempotency-key-required
            - unsupported-version
            - authentication-required
            - account-required
            - insufficient-credits
            - insufficient-scope
            - confirmation-required
            - business-details-required
            - licence-acceptance-required
            - dpa-acceptance-required
            - agent-budget-exceeded
            - not-found
            - method-not-allowed
            - idempotency-key-reused
            - quote-expired
            - order-not-ready
            - order-not-cancellable
            - validation-failed
            - quote-required
            - dataset-unavailable
            - rate-limited
            - internal-error
            - not-implemented
            - service-unavailable
            - tps-screen-expired
          description: The problem type as a stable code.
        next_step:
          type: string
          description: What to do next, in plain English.
        did_you_mean:
          description: The closest valid values, best first.
          type: array
          items:
            type: string
        errors:
          description: "For validation-failed: every problem found, each with a JSON pointer."
          type: array
          items:
            $ref: "#/components/schemas/ProblemError"
        required_scope:
          description: "For insufficient-scope: the scope to request, such as orders:write."
          type: string
        retry_after_seconds:
          description: "For rate-limited and service-unavailable: the same value as the Retry-After header."
          type: integer
          minimum: 0
      required:
        - type
        - title
        - status
        - detail
        - code
        - next_step
      description: An RFC 9457 problem. MCP tool errors carry the same object with isError true.
      examples:
        - 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.
          errors:
            - pointer: /audience/where/all/0/field
              code: invalid_value
              message: Unknown field company.employees.
              did_you_mean:
                - company.employee_band
    ProblemError:
      type: object
      properties:
        pointer:
          type: string
          description: RFC 6901 JSON pointer to the member at fault, such as /audience/where/all/0/field.
        code:
          type: string
          description: What went wrong, such as invalid_value or custom.
        message:
          type: string
        did_you_mean:
          type: array
          items:
            type: string
      required:
        - pointer
        - code
        - message
      description: One validation failure.
    Provenance:
      type: object
      properties:
        company:
          type: object
          properties:
            source:
              type: string
              enum:
                - emailmovers_research
                - companies_house
                - onspd
                - ukbizdirectory
                - partner_rampedup
            as_of:
              type: string
              format: date
          required:
            - source
            - as_of
          description: Where the company facts came from and the snapshot date.
        contact:
          anyOf:
            - type: object
              properties:
                source:
                  type: string
                  enum:
                    - emailmovers_research
                    - companies_house
                    - onspd
                    - ukbizdirectory
                    - partner_rampedup
                compiled_at:
                  type: string
                  format: date
                last_verified_at:
                  anyOf:
                    - type: string
                      format: date
                    - type: "null"
                method:
                  type: string
                  enum:
                    - telephone
                    - web
                    - register
              required:
                - source
                - compiled_at
                - last_verified_at
                - method
            - type: "null"
          description: Who compiled the contact, when, and how it was last confirmed.
        email:
          description: "The latest email verification: date, provider result and location."
          anyOf:
            - type: object
              properties:
                verified_at:
                  type: string
                  format: date
                result:
                  type: string
                  enum:
                    - ok
                    - catch_all
                    - unknown
                    - invalid
                    - disposable
                provider_location:
                  type: string
                  enum:
                    - UK
                    - EU
                    - US
              required:
                - verified_at
                - result
                - provider_location
            - type: "null"
      required:
        - company
        - contact
      description: Source per entity, answering the ICO questions who compiled it and where from.
    ProvenanceCard:
      type: object
      properties:
        sources:
          type: object
          propertyNames:
            type: string
          additionalProperties:
            type: string
            enum:
              - emailmovers_research
              - companies_house
              - onspd
              - ukbizdirectory
              - partner_rampedup
          description: "The source of each field shown, such as title: emailmovers_research."
        first_seen_at:
          type: string
          format: date
        last_verified_at:
          anyOf:
            - type: string
              format: date
            - type: "null"
        method:
          type: string
          enum:
            - telephone
            - web
            - register
        companies_house_number:
          type:
            - string
            - "null"
        company_status:
          type: string
        article14_status:
          type: string
          enum:
            - notified
            - publicly_notified_disproportionate_effort
            - pending_within_window
        tps_ctps_screened_at:
          anyOf:
            - type: string
              format: date
            - type: "null"
        lawful_basis:
          type: string
          enum:
            - legitimate_interests_art6_1_f
      required:
        - sources
        - first_seen_at
        - last_verified_at
        - method
        - companies_house_number
        - company_status
        - article14_status
        - tps_ctps_screened_at
        - lawful_basis
      description: The ICO due diligence answers for one record, as shown on the sample row.
    Purchase:
      type: object
      properties:
        credits_to_buy:
          type: integer
          minimum: 0
          description: "credits_required less the balance: all of it when signed out, 0 when the balance covers the order."
        cheapest:
          anyOf:
            - type: string
              pattern: ^[a-z0-9]+(_[a-z0-9]+)*$
            - type: "null"
          description: The id of the cheapest option; null when nothing needs buying.
        gbp_ex_vat:
          type: number
          minimum: 0
          description: What the cheapest option costs, excluding VAT; 0 when nothing needs buying.
        options:
          type: array
          items:
            $ref: "#/components/schemas/PurchaseOption"
          description: "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."
      required:
        - credits_to_buy
        - cheapest
        - gbp_ex_vat
        - options
      description: "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."
    PurchaseAttention:
      type: object
      properties:
        reason:
          type: string
          enum:
            - checkout_mismatch
            - billed_outside_uk
          description: "checkout_mismatch: Stripe says it is paid but the checkout does not match this purchase, so no credits were added; billed_outside_uk: credited, but the billing address is not in the UK."
        detail:
          type: string
          description: What a person should look at, in plain English.
        since:
          type: string
          format: date-time
      required:
        - reason
        - detail
        - since
      description: A purchase a person at Emailmovers looks at. It is flagged on the purchase, and emailed to the payment alerts address when one is set.
    PurchaseLine:
      type: object
      properties:
        id:
          type: string
          pattern: ^[a-z0-9]+(_[a-z0-9]+)*$
          description: A credit pack id, or per_contact_scale.
        quantity:
          type: integer
          minimum: 0
          description: Credit packs bought, or credits bought at the scale.
        credits:
          type: integer
          minimum: 0
        gbp_ex_vat:
          type: number
          minimum: 0
          description: Pounds sterling excluding VAT, to two decimal places.
      required:
        - id
        - quantity
        - credits
        - gbp_ex_vat
      description: "One thing bought: credit packs, or credits at the scale."
    PurchaseOption:
      type: object
      properties:
        id:
          type: string
          pattern: ^[a-z0-9]+(_[a-z0-9]+)*$
          description: per_contact_scale, or the id of the credit pack the option is built on (with any rest at the scale). The same between quotes, so a choice survives a fresh quote.
        label:
          type: string
          description: Such as 12,000 records at 22p.
        credits:
          type: integer
          minimum: 0
          description: Credits the option buys.
        gbp_ex_vat:
          type: number
          minimum: 0
          description: Pounds sterling excluding VAT, to two decimal places.
        credits_left_over:
          type: integer
          minimum: 0
          description: Credits that stay in the balance after the order.
        lines:
          type: array
          items:
            $ref: "#/components/schemas/PurchaseLine"
      required:
        - id
        - label
        - credits
        - gbp_ex_vat
        - credits_left_over
        - lines
      description: One way to buy the credits the order needs.
    Push:
      type: object
      properties:
        id:
          type: string
          pattern: ^psh_[0-7][0-9A-HJKMNP-TV-Z]{25}$
          description: "Format: a CRM push id: psh_ followed by a 26 character ULID in upper case Crockford base32, such as psh_01JB4NC3W2X6Y7Z8A1B2C3D4E5."
          examples:
            - psh_01JB4NC3W2X6Y7Z8A1B2C3D4E5
        order_id:
          type: string
          pattern: ^ord_[0-7][0-9A-HJKMNP-TV-Z]{25}$
          description: "Format: an order id: ord_ followed by a 26 character ULID in upper case Crockford base32, such as ord_01JB4N9S3W2X6Y7Z8A1B2C3D4E."
          examples:
            - ord_01JB4N9S3W2X6Y7Z8A1B2C3D4E
        connection_id:
          type: string
          pattern: ^conn_[0-7][0-9A-HJKMNP-TV-Z]{25}$
          description: "Format: a CRM connection id: conn_ followed by a 26 character ULID in upper case Crockford base32, such as conn_01JB3Y2X6Y7Z8A1B2C3D4E5F6G."
          examples:
            - conn_01JB3Y2X6Y7Z8A1B2C3D4E5F6G
        dry_run:
          type: boolean
        status:
          type: string
          enum:
            - queued
            - running
            - completed
            - partial
            - failed
        summary:
          type: object
          properties:
            to_create:
              type: integer
              minimum: 0
            to_update:
              type: integer
              minimum: 0
            already_present:
              type: integer
              minimum: 0
            skipped:
              type: integer
              minimum: 0
            failed:
              type: integer
              minimum: 0
          required:
            - to_create
            - to_update
            - already_present
            - skipped
            - failed
        property_group:
          type: array
          items:
            type: string
            enum:
              - mydatahub_record_id
              - licence_expires_at
              - lawful_basis
              - article14_status
              - tps_ctps_screened_at
              - subscriber_class
              - suppression_status
              - compliance_receipt_url
          description: The MyDataHub property group written on every record (CrmPropertyGroup).
        sample:
          anyOf:
            - $ref: "#/components/schemas/CrmPropertyGroup"
            - type: "null"
          description: "On a dry run: the property group the first record would carry."
        errors:
          type: array
          items:
            type: object
            properties:
              record_id:
                type: string
              message:
                type: string
            required:
              - record_id
              - message
        created_at:
          type: string
          format: date-time
        completed_at:
          anyOf:
            - type: string
              format: date-time
            - type: "null"
      required:
        - id
        - order_id
        - connection_id
        - dry_run
        - status
        - summary
        - property_group
        - sample
        - errors
        - created_at
        - completed_at
      description: A CRM push or its dry run, such as 4,102 to create and 0 already in your HubSpot.
    PushCompletedEvent:
      type: object
      properties:
        id:
          type: string
          pattern: ^evt_[0-7][0-9A-HJKMNP-TV-Z]{25}$
          description: Also sent as the webhook-id header; your idempotency key.
          examples:
            - evt_01JB4NB7T1Y5W8ZAK8Q2V7XN3M
        type:
          type: string
          const: push.completed
        timestamp:
          type: string
          format: date-time
        api_version:
          type: string
          pattern: ^[0-9]{4}-(0[1-9]|1[0-2])-(0[1-9]|[12][0-9]|3[01])$
          description: A dated MyDataHub-Version, such as 2027-01-15.
        data:
          $ref: "#/components/schemas/Push"
      required:
        - id
        - type
        - timestamp
        - api_version
        - data
      description: A CRM push finished.
    PushFailedEvent:
      type: object
      properties:
        id:
          type: string
          pattern: ^evt_[0-7][0-9A-HJKMNP-TV-Z]{25}$
          description: Also sent as the webhook-id header; your idempotency key.
          examples:
            - evt_01JB4NB7T1Y5W8ZAK8Q2V7XN3M
        type:
          type: string
          const: push.failed
        timestamp:
          type: string
          format: date-time
        api_version:
          type: string
          pattern: ^[0-9]{4}-(0[1-9]|1[0-2])-(0[1-9]|[12][0-9]|3[01])$
          description: A dated MyDataHub-Version, such as 2027-01-15.
        data:
          $ref: "#/components/schemas/Push"
      required:
        - id
        - type
        - timestamp
        - api_version
        - data
      description: A CRM push failed.
    PushPartialEvent:
      type: object
      properties:
        id:
          type: string
          pattern: ^evt_[0-7][0-9A-HJKMNP-TV-Z]{25}$
          description: Also sent as the webhook-id header; your idempotency key.
          examples:
            - evt_01JB4NB7T1Y5W8ZAK8Q2V7XN3M
        type:
          type: string
          const: push.partial
        timestamp:
          type: string
          format: date-time
        api_version:
          type: string
          pattern: ^[0-9]{4}-(0[1-9]|1[0-2])-(0[1-9]|[12][0-9]|3[01])$
          description: A dated MyDataHub-Version, such as 2027-01-15.
        data:
          $ref: "#/components/schemas/Push"
      required:
        - id
        - type
        - timestamp
        - api_version
        - data
      description: A CRM push partly finished; retry the failed records.
    PushRequest:
      type: object
      properties:
        connection_id:
          type: string
          pattern: ^conn_[0-7][0-9A-HJKMNP-TV-Z]{25}$
          description: "Format: a CRM connection id: conn_ followed by a 26 character ULID in upper case Crockford base32, such as conn_01JB3Y2X6Y7Z8A1B2C3D4E5F6G."
          examples:
            - conn_01JB3Y2X6Y7Z8A1B2C3D4E5F6G
        dry_run:
          description: "Default true: report what would change without writing. A live push needs approval: a person's confirmation, or an agent key allowed push_to_crm on this connection."
          default: true
          type: boolean
        update_existing:
          description: Update records already in the CRM, matched on mydatahub_record_id. Default true.
          default: true
          type: boolean
        confirmation_token:
          type: string
          minLength: 16
          maxLength: 2000
          description: The token the approval page on mydatahub.emailmovers.com returns once the signed-in person approves. It is bound to that person (their sub), this spend (the machine quote_id, or the enrichment or push) and an expiry no later than the quote's, and it is redeemable once.
      required:
        - connection_id
      additionalProperties: false
      description: Push an order's records to a connection. Records only; nothing is ever sent to the contacts. A live push without approval returns 403 confirmation-required.
    Quality:
      type: object
      properties:
        email_coverage_pct:
          type: number
          minimum: 0
          maximum: 100
          description: Records with a named email as a share of the count.
        verified_90d_pct:
          type: number
          minimum: 0
          maximum: 100
          description: Share of emails with a provider result of ok in the last 90 days.
        verified_measured_at:
          type: string
          format: date
        deliverability:
          description: "Score distribution: good 70 to 100, risky 40 to 69, poor under 40."
          type: object
          properties:
            good:
              type: number
              minimum: 0
              maximum: 100
            risky:
              type: number
              minimum: 0
              maximum: 100
            poor:
              type: number
              minimum: 0
              maximum: 100
          required:
            - good
            - risky
            - poor
        avg_days_since_verification:
          type: number
          minimum: 0
        phone_coverage_pct:
          description: Share with a validated number, when phone is requested.
          type: number
          minimum: 0
          maximum: 100
        tps_ctps_screened_at:
          description: The version's TPS and CTPS screen date, when phone is requested and a number in it is screened; each number carries its own status and screen date.
          type: string
          format: date
      required:
        - email_coverage_pct
        - verified_90d_pct
      description: Quality signals shown with every count.
    Quote:
      oneOf:
        - $ref: "#/components/schemas/MachineQuote"
        - $ref: "#/components/schemas/SalesQuote"
      description: A machine quote (kind machine, 24 hours) or a human sales quote (kind sales, 14 days).
      discriminator:
        propertyName: kind
        mapping:
          machine: "#/components/schemas/MachineQuote"
          sales: "#/components/schemas/SalesQuote"
    QuoteBlocker:
      type: object
      properties:
        code:
          type: string
          enum:
            - insufficient_credits
            - account_required
            - agent_budget_exceeded
            - dataset_unavailable
            - count_expired
        detail:
          type: string
        top_up_url:
          type: string
          format: uri
      required:
        - code
        - detail
      description: Something that stops create_order until it is fixed.
    QuoteRequest:
      type: object
      properties:
        count_id:
          type: string
          pattern: ^cnt_[A-Za-z2-7]{20}$
          description: A count id from POST /v1/counts or count_audience. Either case is accepted; responses use upper case.
          examples:
            - cnt_7K2PQ4MAXN3R6TBY5WDZ
        field_set:
          type: string
          pattern: ^[a-z0-9]+(_[a-z0-9]+)*$
          description: A field set id from GET /v1/field-sets, such as standard.
        limit:
          description: Records to buy; left out, the whole count.
          type: integer
          minimum: 1
          maximum: 1000000
        dedupe_against:
          description: "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
          type: string
          enum:
            - none
            - previous_orders
            - crm
            - previous_orders_and_crm
        connection_id:
          description: The CRM connection to check, only with dedupe_against crm or previous_orders_and_crm.
          type: string
          pattern: ^conn_[0-7][0-9A-HJKMNP-TV-Z]{25}$
          examples:
            - conn_01JB3Y2X6Y7Z8A1B2C3D4E5F6G
        licence:
          default: multi_use_12m
          type: string
          enum:
            - multi_use_12m
            - outright
          description: multi_use_12m (12 month multi-use, every plan) or outright (1.8 credits a record, proposed).
      required:
        - count_id
        - field_set
      additionalProperties: false
      description: "What to price: a count, a field set, how many records and the licence. Free, and needs no account."
      allOf:
        - if:
            type: object
            properties:
              dedupe_against:
                enum:
                  - crm
                  - previous_orders_and_crm
            required:
              - dedupe_against
          then:
            type: object
            properties:
              connection_id: true
            required:
              - connection_id
        - if:
            type: object
            properties:
              connection_id: true
            required:
              - connection_id
          then:
            type: object
            properties:
              dedupe_against:
                enum:
                  - crm
                  - previous_orders_and_crm
            required:
              - dedupe_against
    QuoteSignature:
      type: object
      properties:
        alg:
          type: string
          const: ES256
        kid:
          type: string
          description: The signing key id in jwks_url.
        jwks_url:
          type: string
          format: uri
        jws:
          type: string
          pattern: ^[A-Za-z0-9_-]+\.[A-Za-z0-9_-]+\.[A-Za-z0-9_-]+$
          description: 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.
      required:
        - alg
        - kid
        - jwks_url
        - jws
      description: 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.
    RadiusCentre:
      anyOf:
        - $ref: "#/components/schemas/PlaceCentre"
        - $ref: "#/components/schemas/PostcodeCentre"
        - $ref: "#/components/schemas/PointCentre"
      description: "The centre of a radius: a place, a postcode or a latitude and longitude."
    RateLimit:
      type: object
      properties:
        tier:
          type: string
          enum:
            - anonymous
            - free
            - paid
        counts_per_minute:
          anyOf:
            - type: integer
              minimum: 0
            - type: "null"
        counts_per_hour:
          anyOf:
            - type: integer
              minimum: 0
            - type: "null"
        counts_per_day:
          anyOf:
            - type: integer
              minimum: 0
            - type: "null"
        remaining:
          type: integer
          minimum: 0
        resets_at:
          type: string
          format: date-time
        status:
          type: string
          enum:
            - confirmed
            - to_confirm
      required:
        - tier
        - counts_per_minute
        - counts_per_hour
        - counts_per_day
        - remaining
        - resets_at
        - status
      description: The caller's count limits and what remains in the window.
    ReceiptClaims:
      type: object
      properties:
        receipt_id:
          type: string
          pattern: ^rcpt_[0-7][0-9A-HJKMNP-TV-Z]{25}$
          description: "Format: a compliance receipt id: rcpt_ followed by a 26 character ULID in upper case Crockford base32, such as rcpt_01JB4NA2Q5W8ZAK8Q2V7XN3M9R."
          examples:
            - rcpt_01JB4NA2Q5W8ZAK8Q2V7XN3M9R
        order_id:
          type: string
          pattern: ^ord_[0-7][0-9A-HJKMNP-TV-Z]{25}$
          description: "Format: an order id: ord_ followed by a 26 character ULID in upper case Crockford base32, such as ord_01JB4N9S3W2X6Y7Z8A1B2C3D4E."
          examples:
            - ord_01JB4N9S3W2X6Y7Z8A1B2C3D4E
        issued_at:
          type: string
          format: date-time
        dataset_version:
          type: string
          pattern: ^[0-9]{4}-W(0[1-9]|[1-4][0-9]|5[0-3])$
          description: The immutable weekly build a count is bound to, such as 2026-W39. The last 52 stay queryable.
          examples:
            - 2026-W39
        records_delivered:
          type: integer
          minimum: 0
        removed_by_suppression:
          type: integer
          minimum: 0
        counts:
          type: object
          properties:
            subscriber_class:
              type: object
              properties:
                ltd:
                  type: integer
                  minimum: 0
                plc:
                  type: integer
                  minimum: 0
                llp:
                  type: integer
                  minimum: 0
                scottish_partnership:
                  type: integer
                  minimum: 0
                public_body:
                  type: integer
                  minimum: 0
                charity_incorporated:
                  type: integer
                  minimum: 0
                sole_trader:
                  type: integer
                  minimum: 0
                ordinary_partnership:
                  type: integer
                  minimum: 0
                unknown:
                  type: integer
                  minimum: 0
              required:
                - ltd
                - plc
                - llp
                - scottish_partnership
                - public_body
                - charity_incorporated
                - sole_trader
                - ordinary_partnership
                - unknown
            tps_ctps_status:
              type: object
              properties:
                clear:
                  type: integer
                  minimum: 0
                tps_registered:
                  type: integer
                  minimum: 0
                ctps_registered:
                  type: integer
                  minimum: 0
                tps_or_ctps_registered:
                  type: integer
                  minimum: 0
                not_screened:
                  type: integer
                  minimum: 0
              required:
                - clear
                - tps_registered
                - ctps_registered
                - tps_or_ctps_registered
                - not_screened
            article14_status:
              type: object
              properties:
                notified:
                  type: integer
                  minimum: 0
                publicly_notified_disproportionate_effort:
                  type: integer
                  minimum: 0
                pending_within_window:
                  type: integer
                  minimum: 0
              required:
                - notified
                - publicly_notified_disproportionate_effort
                - pending_within_window
          required:
            - subscriber_class
            - tps_ctps_status
            - article14_status
        lawful_basis:
          type: string
          enum:
            - legitimate_interests_art6_1_f
        lia_ref:
          type: string
        licence:
          $ref: "#/components/schemas/Licence"
        residency_statement_version:
          type: string
        sub_processor_list_version:
          type: string
        verified_within_days:
          type: object
          properties:
            within_30_days:
              type: number
              minimum: 0
              maximum: 100
            within_60_days:
              type: number
              minimum: 0
              maximum: 100
            within_90_days:
              type: number
              minimum: 0
              maximum: 100
          required:
            - within_30_days
            - within_60_days
            - within_90_days
          description: Share of delivered emails verified within 30, 60 and 90 days.
        content_sha256:
          type: string
          pattern: ^[0-9a-f]{64}$
          description: SHA-256 of the delivered file, lower case hex.
      required:
        - receipt_id
        - order_id
        - issued_at
        - dataset_version
        - records_delivered
        - removed_by_suppression
        - counts
        - lawful_basis
        - lia_ref
        - licence
        - residency_statement_version
        - sub_processor_list_version
        - verified_within_days
        - content_sha256
      description: The decoded payload of a compliance receipt.
    ReceiptVerification:
      type: object
      properties:
        valid:
          type: boolean
          description: The signature checks out against the published key.
        order_exists:
          type: boolean
        licence_in_force_until:
          anyOf:
            - type: string
              format: date
            - type: "null"
        revoked:
          type: boolean
        receipt_id:
          anyOf:
            - type: string
              pattern: ^rcpt_[0-7][0-9A-HJKMNP-TV-Z]{25}$
              description: "Format: a compliance receipt id: rcpt_ followed by a 26 character ULID in upper case Crockford base32, such as rcpt_01JB4NA2Q5W8ZAK8Q2V7XN3M9R."
              examples:
                - rcpt_01JB4NA2Q5W8ZAK8Q2V7XN3M9R
            - type: "null"
        checked_at:
          type: string
          format: date-time
      required:
        - valid
        - order_exists
        - licence_in_force_until
        - revoked
        - receipt_id
        - checked_at
      description: The verdict on a receipt, so one agent can prove to another a list is licensed.
    ReceiptVerifyRequest:
      type: object
      properties:
        jws:
          type: string
          minLength: 10
          maxLength: 20000
          description: The compact JWS to check.
      required:
        - jws
      additionalProperties: false
      description: A compliance receipt to verify.
    RecordsPage:
      type: object
      properties:
        order_id:
          type: string
          pattern: ^ord_[0-7][0-9A-HJKMNP-TV-Z]{25}$
          description: "Format: an order id: ord_ followed by a 26 character ULID in upper case Crockford base32, such as ord_01JB4N9S3W2X6Y7Z8A1B2C3D4E."
          examples:
            - ord_01JB4N9S3W2X6Y7Z8A1B2C3D4E
        format:
          type: string
          const: json
        fields:
          type: array
          items:
            type: string
            pattern: ^[a-z0-9]+(_[a-z0-9]+)*$
          description: The field set's columns in this page.
        data:
          type: array
          items:
            $ref: "#/components/schemas/ContactRecord"
        has_more:
          type: boolean
        next_cursor:
          anyOf:
            - type: string
              minLength: 1
              maxLength: 512
              description: An opaque cursor from next_cursor. Pass it back unchanged.
            - type: "null"
        remaining:
          type: integer
          minimum: 0
          description: Records after this page.
        approx_tokens:
          description: A rough token size of this page; pages stay under about 8,000 tokens.
          type: integer
          minimum: 0
        licence:
          $ref: "#/components/schemas/Licence"
      required:
        - order_id
        - format
        - fields
        - data
        - has_more
        - next_cursor
        - remaining
        - licence
      description: A page of delivered records with the licence block. Re-fetching records under licence costs 0 credits.
    ResolvedCentre:
      type: object
      properties:
        label:
          type: string
        code:
          type:
            - string
            - "null"
        lat:
          type: number
        lon:
          type: number
        radius_miles:
          type: number
        postcode_districts_matched:
          type: integer
          minimum: 0
      required:
        - label
        - code
        - lat
        - lon
        - radius_miles
        - postcode_districts_matched
      description: A radius centre as the engine resolved it.
    SalesQuote:
      type: object
      properties:
        id:
          type: string
          pattern: ^qte_[0-7][0-9A-HJKMNP-TV-Z]{25}$
          description: "Format: a quote id: qte_ followed by a 26 character ULID in upper case Crockford base32, such as qte_01JB4M2R6T1Y5W8ZAK8Q2V7XN3."
          examples:
            - qte_01JB4M2R6T1Y5W8ZAK8Q2V7XN3
        kind:
          type: string
          const: sales
        status:
          type: string
          enum:
            - draft
            - sent
            - accepted
            - paid
            - expired
            - withdrawn
        created_at:
          type: string
          format: date-time
        valid_until:
          type: string
          format: date
          description: 14 days after creation.
        created_by:
          type: object
          properties:
            role:
              type: string
              enum:
                - user
                - account_manager
            name:
              type: string
          required:
            - role
            - name
        customer:
          type: string
          description: The organisation the quote is for.
        count_id:
          anyOf:
            - type: string
              pattern: ^cnt_[A-Z2-7]{20}$
              description: "Format: a count id: cnt_ followed by 20 base32 characters (A to Z and 2 to 7), such as cnt_7K2PQ4MAXN3R6TBY5WDZ."
              examples:
                - cnt_7K2PQ4MAXN3R6TBY5WDZ
            - type: "null"
        field_set:
          type: string
          pattern: ^[a-z0-9]+(_[a-z0-9]+)*$
        records:
          type: integer
          minimum: 0
          description: Records the quote covers.
        credits_per_record:
          type: number
          minimum: 0
          description: "Credits a record under the licence: 1, or 1.8 outright (proposed)."
        lines:
          type: array
          items:
            $ref: "#/components/schemas/SalesQuoteLine"
        subtotal_gbp_ex_vat:
          type: number
          minimum: 0
          description: Pounds sterling excluding VAT, to two decimal places.
        vat_gbp:
          type: number
          minimum: 0
          description: Pounds sterling excluding VAT, to two decimal places.
        total_gbp_inc_vat:
          type: number
          minimum: 0
          description: Pounds sterling excluding VAT, to two decimal places.
        balance_after:
          description: 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.
          type:
            - number
            - "null"
        licence:
          $ref: "#/components/schemas/Licence"
        warranty:
          $ref: "#/components/schemas/Warranty"
        payment_methods:
          type: array
          items:
            type: string
            enum:
              - card
              - invoice
        pay_url:
          type: string
          format: uri
          description: Pay by card or request an invoice link, no account needed.
        pdf_url:
          type: string
          format: uri
      required:
        - id
        - kind
        - status
        - created_at
        - valid_until
        - created_by
        - customer
        - count_id
        - field_set
        - records
        - credits_per_record
        - lines
        - subtotal_gbp_ex_vat
        - vat_gbp
        - total_gbp_inc_vat
        - balance_after
        - licence
        - warranty
        - payment_methods
        - pay_url
        - pdf_url
      description: "A human sales quote: 14 days, a PDF, payable by card or invoice link, created in the app or by an account manager. Not a machine quote; create_order does not take it."
    SalesQuoteLine:
      type: object
      properties:
        description:
          type: string
        quantity:
          type: integer
          minimum: 0
        unit_price_gbp:
          type: number
          minimum: 0
          description: Pounds sterling excluding VAT, to two decimal places.
        amount_gbp:
          type: number
          minimum: 0
          description: Pounds sterling excluding VAT, to two decimal places.
      required:
        - description
        - quantity
        - unit_price_gbp
        - amount_gbp
      description: One line of a sales quote, excluding VAT.
    SampleDownload:
      type: object
      properties:
        status:
          type: string
          enum:
            - ready
            - empty
            - downloaded
          description: "ready: download_url serves the sample once; empty: no record of the audience can be sent as a sample now, and nothing is supplied; downloaded: the sample was already served, and is never served twice."
        records:
          type: integer
          minimum: 0
          maximum: 10
          description: "Records in the sample: up to 10, fewer when fewer are left."
        download_url:
          description: A relative link on this host to the CSV, whose first lines are the licence. It works once, for 5 minutes, from the address that asked for it. Null unless ready.
          type:
            - string
            - "null"
        expires_at:
          anyOf:
            - type: string
              format: date-time
            - type: "null"
          description: When download_url stops working. Null unless ready.
      required:
        - status
        - records
        - download_url
        - expires_at
      description: "The confirmed sample: a single-use link to it, or why there is none. Confirming chooses the records, read afresh against every suppression."
    SampleDownloadCreate:
      type: object
      properties:
        token:
          type: string
          pattern: ^[A-Za-z0-9_-]{40,64}$
          description: The token from the emailed link (the last part of /samples/confirm/{token}). Only a press of the confirmation page's button sends it, so a link scanner that opens the page uses nothing up.
      required:
        - token
      additionalProperties: false
      description: The emailed token.
    SampleRequest:
      type: object
      properties:
        id:
          type: string
          pattern: ^smr_[0-7][0-9A-HJKMNP-TV-Z]{25}$
          description: "Format: a sample request id: smr_ followed by a 26 character ULID in upper case Crockford base32, such as smr_01JB4P2Q5W8ZAK8Q2V7XN3M9R6."
          examples:
            - smr_01JB4P2Q5W8ZAK8Q2V7XN3M9R6
        count_id:
          type: string
          pattern: ^cnt_[A-Z2-7]{20}$
          description: "Format: a count id: cnt_ followed by 20 base32 characters (A to Z and 2 to 7), such as cnt_7K2PQ4MAXN3R6TBY5WDZ."
          examples:
            - cnt_7K2PQ4MAXN3R6TBY5WDZ
        status:
          type: string
          enum:
            - awaiting_email
            - email_sent
            - confirmed
            - downloaded
            - expired
          description: awaiting_email until the address is sent; email_sent once it was checked (a refused address reads the same); confirmed once the link is opened; downloaded once the sample is served; expired after its time.
        created_at:
          type: string
          format: date-time
        expires_at:
          type: string
          format: date-time
          description: "When the request, or the link emailed for it, stops working: 24 hours."
      required:
        - id
        - count_id
        - status
        - created_at
        - expires_at
      description: A request for a sample of a counted audience. It never shows the address or the records.
    SampleRequestCreate:
      type: object
      properties:
        count_id:
          type: string
          pattern: ^cnt_[A-Z2-7]{20}$
          description: "Format: a count id: cnt_ followed by 20 base32 characters (A to Z and 2 to 7), such as cnt_7K2PQ4MAXN3R6TBY5WDZ."
          examples:
            - cnt_7K2PQ4MAXN3R6TBY5WDZ
      required:
        - count_id
      additionalProperties: false
      description: The count to sample.
    SampleRequestEmail:
      type: object
      properties:
        email:
          type: string
          maxLength: 254
          format: email
          description: The person's work email address, sent by the calling server from its own form, never through a model. Free webmail addresses are refused.
        follow_up:
          type: boolean
          description: Whether the person ticked the box allowing Emailmovers to email them about the sample.
        wording:
          type: string
          minLength: 1
          maxLength: 40
          description: The version of the wording the person saw beside the field and the box.
      required:
        - email
        - follow_up
        - wording
      additionalProperties: false
      description: The address to confirm, and the person's choice on follow-up.
    SampleRequestEmailAccepted:
      type: object
      properties:
        status:
          type: string
          const: check_your_inbox
          description: The same answer whether the address was accepted or refused (a suppressed address, an objection, a limit), so the call cannot be used to test addresses.
      required:
        - status
      description: The request was taken.
    SavedAudience:
      type: object
      properties:
        id:
          type: string
          pattern: ^aud_[0-7][0-9A-HJKMNP-TV-Z]{25}$
          description: "Format: an audience id: aud_ followed by a 26 character ULID in upper case Crockford base32, such as aud_01JB4H2K9V6XN3M9R6T1Y5W8ZA."
          examples:
            - aud_01JB4H2K9V6XN3M9R6T1Y5W8ZA
        name:
          type: string
        audience:
          $ref: "#/components/schemas/Audience"
        brief:
          description: The brief it was compiled from, if any.
          type:
            - string
            - "null"
        current_count:
          anyOf:
            - $ref: "#/components/schemas/AudienceCountSummary"
            - type: "null"
        last_refreshed_at:
          anyOf:
            - type: string
              format: date-time
            - type: "null"
        created_at:
          type: string
          format: date-time
        updated_at:
          type: string
          format: date-time
        created_by:
          type: string
          pattern: ^usr_[0-7][0-9A-HJKMNP-TV-Z]{25}$
          description: "Format: a user id: usr_ followed by a 26 character ULID in upper case Crockford base32, such as usr_01JA0Q3V7XN3M9R6T1Y5W8ZAK8."
          examples:
            - usr_01JA0Q3V7XN3M9R6T1Y5W8ZAK8
        url:
          type: string
          format: uri
          description: The audience in the app, mydatahub.emailmovers.com/audiences/{id}.
      required:
        - id
        - name
        - audience
        - brief
        - current_count
        - last_refreshed_at
        - created_at
        - updated_at
        - created_by
        - url
      description: A named audience shared across the organisation, with its current count.
    SavedAudienceList:
      type: object
      properties:
        data:
          type: array
          items:
            $ref: "#/components/schemas/SavedAudience"
        has_more:
          type: boolean
          description: Whether another page follows.
        next_cursor:
          anyOf:
            - type: string
              minLength: 1
              maxLength: 512
              description: An opaque cursor from next_cursor. Pass it back unchanged.
            - type: "null"
          description: Pass as cursor to fetch the next page; null on the last page.
      required:
        - data
        - has_more
        - next_cursor
      description: A page of saved audiences, newest first.
    SavedAudienceRequest:
      type: object
      properties:
        name:
          type: string
          minLength: 1
          maxLength: 200
        audience:
          $ref: "#/components/schemas/Audience"
        brief:
          type: string
          maxLength: 2000
      required:
        - name
        - audience
      additionalProperties: false
      description: A named audience to save.
    SavedAudienceUpdate:
      type: object
      properties:
        name:
          type: string
          minLength: 1
          maxLength: 200
        audience:
          $ref: "#/components/schemas/Audience"
        brief:
          anyOf:
            - type: string
              maxLength: 2000
            - type: "null"
      additionalProperties: false
      description: Members to change on a saved audience, at least one.
      minProperties: 1
    SicCode:
      type: object
      properties:
        code:
          type: string
        label:
          type: string
      required:
        - code
        - label
      description: A SIC 2007 code and its label, such as 25620 Machining.
    SinceFilter:
      type: object
      properties:
        field:
          type: string
          enum:
            - contact.last_validated_at
            - email.last_verified_at
            - signal.type
            - signal.observed_at
        op:
          type: string
          const: since
        days:
          type: integer
          minimum: 1
          maximum: 3650
        values:
          description: "For signal.type only: the signal types to look for, such as company.incorporated."
          minItems: 1
          maxItems: 10000
          type: array
          items:
            type: string
            minLength: 1
            maxLength: 200
      required:
        - field
        - op
        - days
      additionalProperties: false
      description: "Within the last N days: a date field, or a Companies House signal of the listed types on signal.type."
      examples:
        - field: signal.type
          op: since
          values:
            - company.incorporated
          days: 90
      allOf:
        - if:
            type: object
            properties:
              field:
                const: contact.last_validated_at
            required:
              - field
          then:
            type: object
            properties:
              values: false
        - if:
            type: object
            properties:
              field:
                const: email.last_verified_at
            required:
              - field
          then:
            type: object
            properties:
              values: false
        - if:
            type: object
            properties:
              field:
                const: signal.type
            required:
              - field
          then:
            type: object
            required:
              - values
            properties:
              values:
                type: array
                items:
                  type: string
                  enum:
                    - company.incorporated
                    - company.name_changed
                    - company.registered_office_moved
                    - company.sic_changed
                    - company.charge_created
                    - company.accounts_overdue
                    - company.confirmation_statement_overdue
        - if:
            type: object
            properties:
              field:
                const: signal.observed_at
            required:
              - field
          then:
            type: object
            properties:
              values: false
    StandardApiKey:
      type: object
      properties:
        id:
          type: string
          pattern: ^key_[0-7][0-9A-HJKMNP-TV-Z]{25}$
          description: "Format: an API key id: key_ followed by a 26 character ULID in upper case Crockford base32, such as key_01JB3Z4Q2V7XN3M9R6T1Y5W8ZA."
          examples:
            - key_01JB3Z4Q2V7XN3M9R6T1Y5W8ZA
        label:
          type: string
        mode:
          type: string
          enum:
            - live
            - test
          description: live keys (mdh_live_) spend real credits; test keys (mdh_test_) return synthetic data and never charge.
        prefix:
          type: string
          description: The first characters of the secret, such as mdh_live_7Kq2.
        scopes:
          type: array
          items:
            type: string
            enum:
              - account:read
              - counts:read
              - previews:read
              - audiences:read
              - audiences:write
              - quotes:read
              - orders:read
              - orders:write
              - records:read
              - enrich:write
              - suppressions:read
              - suppressions:write
              - pushes:write
              - webhooks:write
              - samples:request
              - admin
        ip_allowlist:
          type: array
          items:
            anyOf:
              - type: string
                pattern: ^((25[0-5]|2[0-4][0-9]|1[0-9][0-9]|[1-9][0-9]|[0-9])\.){3}(25[0-5]|2[0-4][0-9]|1[0-9][0-9]|[1-9][0-9]|[0-9])\/([0-9]|[1-2][0-9]|3[0-2])$
              - type: string
                pattern: ^(([0-9a-fA-F]{1,4}:){7}[0-9a-fA-F]{1,4}|([0-9a-fA-F]{1,4}:){1,7}:|([0-9a-fA-F]{1,4}:){1,6}:[0-9a-fA-F]{1,4}|([0-9a-fA-F]{1,4}:){1,5}(:[0-9a-fA-F]{1,4}){1,2}|([0-9a-fA-F]{1,4}:){1,4}(:[0-9a-fA-F]{1,4}){1,3}|([0-9a-fA-F]{1,4}:){1,3}(:[0-9a-fA-F]{1,4}){1,4}|([0-9a-fA-F]{1,4}:){1,2}(:[0-9a-fA-F]{1,4}){1,5}|[0-9a-fA-F]{1,4}:((:[0-9a-fA-F]{1,4}){1,6})|:((:[0-9a-fA-F]{1,4}){1,7}|:))\/(12[0-8]|1[01][0-9]|[1-9]?[0-9])$
            description: An IPv4 or IPv6 range in CIDR notation, such as 203.0.113.0/24.
          description: CIDR ranges the key may be used from; empty allows any address.
        created_at:
          type: string
          format: date-time
        created_by:
          type: string
          pattern: ^usr_[0-7][0-9A-HJKMNP-TV-Z]{25}$
          description: "Format: a user id: usr_ followed by a 26 character ULID in upper case Crockford base32, such as usr_01JA0Q3V7XN3M9R6T1Y5W8ZAK8."
          examples:
            - usr_01JA0Q3V7XN3M9R6T1Y5W8ZAK8
        last_used_at:
          anyOf:
            - type: string
              format: date-time
            - type: "null"
        expires_at:
          anyOf:
            - type: string
              format: date-time
            - type: "null"
        revoked_at:
          anyOf:
            - type: string
              format: date-time
            - type: "null"
        kind:
          type: string
          const: standard
      required:
        - id
        - label
        - mode
        - prefix
        - scopes
        - ip_allowlist
        - created_at
        - created_by
        - last_used_at
        - expires_at
        - revoked_at
        - kind
      description: An API key for your own code.
    SubProcessor:
      type: object
      properties:
        name:
          type: string
        purpose:
          type: string
        location:
          type: string
        transfer_mechanism:
          type: string
        added_at:
          type: string
          format: date
      required:
        - name
        - purpose
        - location
        - transfer_mechanism
        - added_at
      description: One sub-processor on the trust page.
    SubProcessorChange:
      type: object
      properties:
        date:
          type: string
          format: date
        name:
          type: string
        change:
          type: string
          enum:
            - added
            - removed
            - changed
        summary:
          type: string
      required:
        - date
        - name
        - change
        - summary
      description: One entry in the change log.
    SuppressionCheck:
      type: object
      properties:
        checked_at:
          type: string
          format: date-time
        results:
          type: array
          items:
            type: object
            properties:
              record_id:
                type: string
                pattern: ^contact_[0-7][0-9A-HJKMNP-TV-Z]{25}$
                description: "Format: a contact id: contact_ followed by a 26 character ULID in upper case Crockford base32, such as contact_01JA9X2V7XN3M9R6T1Y5W8ZAK8."
                examples:
                  - contact_01JA9X2V7XN3M9R6T1Y5W8ZAK8
              suppressed:
                type: boolean
              suppression_status:
                type: string
                enum:
                  - none
                  - objected_to_emailmovers
                  - objected_to_licensee
                  - deceased
                  - left_company
              list_ids:
                type: array
                items:
                  type: string
                  pattern: ^sup_[0-7][0-9A-HJKMNP-TV-Z]{25}$
                  description: "Format: a suppression list id: sup_ followed by a 26 character ULID in upper case Crockford base32, such as sup_01JB4H7Q2V7XN3M9R6T1Y5W8ZA."
                  examples:
                    - sup_01JB4H7Q2V7XN3M9R6T1Y5W8ZA
            required:
              - record_id
              - suppressed
              - suppression_status
              - list_ids
      required:
        - checked_at
        - results
      description: Suppression status per record, as of now.
    SuppressionCheckRequest:
      type: object
      properties:
        record_ids:
          minItems: 1
          maxItems: 1000
          type: array
          items:
            type: string
            pattern: ^contact_[0-7][0-9A-HJKMNP-TV-Z]{25}$
            description: "Format: a contact id: contact_ followed by a 26 character ULID in upper case Crockford base32, such as contact_01JA9X2V7XN3M9R6T1Y5W8ZAK8."
            examples:
              - contact_01JA9X2V7XN3M9R6T1Y5W8ZAK8
      required:
        - record_ids
      additionalProperties: false
      description: Records to check before a send.
    SuppressionChecksum:
      type: object
      properties:
        list_id:
          type: string
          pattern: ^sup_[0-7][0-9A-HJKMNP-TV-Z]{25}$
          description: "Format: a suppression list id: sup_ followed by a 26 character ULID in upper case Crockford base32, such as sup_01JB4H7Q2V7XN3M9R6T1Y5W8ZA."
          examples:
            - sup_01JB4H7Q2V7XN3M9R6T1Y5W8ZA
        checksum:
          type: string
        row_count:
          type: integer
          minimum: 0
      required:
        - list_id
        - checksum
        - row_count
      description: A suppression list as the count read it.
    SuppressionCreatedEvent:
      type: object
      properties:
        id:
          type: string
          pattern: ^evt_[0-7][0-9A-HJKMNP-TV-Z]{25}$
          description: Also sent as the webhook-id header; your idempotency key.
          examples:
            - evt_01JB4NB7T1Y5W8ZAK8Q2V7XN3M
        type:
          type: string
          const: suppression.created
        timestamp:
          type: string
          format: date-time
        api_version:
          type: string
          pattern: ^[0-9]{4}-(0[1-9]|1[0-2])-(0[1-9]|[12][0-9]|3[01])$
          description: A dated MyDataHub-Version, such as 2027-01-15.
        data:
          $ref: "#/components/schemas/SuppressionNotice"
      required:
        - id
        - type
        - timestamp
        - api_version
        - data
      description: An objection reached a record your organisation holds. Suppress it before any send.
    SuppressionList:
      type: object
      properties:
        id:
          type: string
          pattern: ^sup_[0-7][0-9A-HJKMNP-TV-Z]{25}$
          description: "Format: a suppression list id: sup_ followed by a 26 character ULID in upper case Crockford base32, such as sup_01JB4H7Q2V7XN3M9R6T1Y5W8ZA."
          examples:
            - sup_01JB4H7Q2V7XN3M9R6T1Y5W8ZA
        name:
          type: string
        kind:
          type: string
          enum:
            - email
            - domain
            - phone
            - company_number
            - title_term
        source:
          type: string
          enum:
            - upload
            - objections
          description: upload for a list you sent; objections for the organisation's own list, which gathers objections you report and database-wide objections on records you hold.
        row_count:
          type: integer
          minimum: 0
        created_at:
          type: string
          format: date-time
        updated_at:
          type: string
          format: date-time
        created_by:
          anyOf:
            - type: string
              pattern: ^usr_[0-7][0-9A-HJKMNP-TV-Z]{25}$
              description: "Format: a user id: usr_ followed by a 26 character ULID in upper case Crockford base32, such as usr_01JA0Q3V7XN3M9R6T1Y5W8ZAK8."
              examples:
                - usr_01JA0Q3V7XN3M9R6T1Y5W8ZAK8
            - type: "null"
        added_since:
          description: "With since: the records added to this list after that time, for pull."
          type: array
          items:
            $ref: "#/components/schemas/SuppressionNotice"
      required:
        - id
        - name
        - kind
        - source
        - row_count
        - created_at
        - updated_at
        - created_by
      description: A named list of hashed emails, domains, phones, company numbers or title terms, applied to every count and order at no charge.
    SuppressionListPage:
      type: object
      properties:
        data:
          type: array
          items:
            $ref: "#/components/schemas/SuppressionList"
        has_more:
          type: boolean
        next_cursor:
          anyOf:
            - type: string
              minLength: 1
              maxLength: 512
              description: An opaque cursor from next_cursor. Pass it back unchanged.
            - type: "null"
      required:
        - data
        - has_more
        - next_cursor
      description: A page of suppression lists.
    SuppressionListRequest:
      type: object
      properties:
        name:
          type: string
          minLength: 1
          maxLength: 200
        kind:
          type: string
          enum:
            - email
            - domain
            - phone
            - company_number
            - title_term
        values:
          minItems: 1
          maxItems: 100000
          type: array
          items:
            type: string
            minLength: 1
            maxLength: 320
          description: Raw values (trimmed, lower cased and hashed on arrival) or SHA-256 hex hashes. Domains may use a wildcard such as *.gmail.com; phones are E.164; company numbers are eight characters.
        hashed:
          description: True when values are already SHA-256 hashes, each 64 lower case hex characters. Default false.
          default: false
          type: boolean
        dpa_accepted_version:
          description: "The version of the Article 28 data processing agreement the person uploading accepts: the account's agreements.dpa.version. The app sends it with the first upload, and again when a new version is in force. Only a signed-in person accepts it."
          type: string
          minLength: 1
          maxLength: 100
      required:
        - name
        - kind
        - values
      additionalProperties: false
      description: A list to upload. Values are stored as SHA-256 hashes only. The first upload asks you to accept the Article 28 DPA in the app, and the account's agreements.dpa keeps who accepted which version and when.
      allOf:
        - if:
            type: object
            properties:
              hashed:
                const: true
            required:
              - hashed
          then:
            type: object
            properties:
              values:
                type: array
                items:
                  type: string
                  pattern: ^[0-9a-f]{64}$
    SuppressionNotice:
      type: object
      properties:
        record_id:
          type: string
          pattern: ^contact_[0-7][0-9A-HJKMNP-TV-Z]{25}$
          description: "Format: a contact id: contact_ followed by a 26 character ULID in upper case Crockford base32, such as contact_01JA9X2V7XN3M9R6T1Y5W8ZAK8."
          examples:
            - contact_01JA9X2V7XN3M9R6T1Y5W8ZAK8
        reason:
          type: string
          enum:
            - none
            - objected_to_emailmovers
            - objected_to_licensee
            - deceased
            - left_company
        scope:
          type: string
          enum:
            - licensee
            - emailmovers
        suppressed_at:
          type: string
          format: date-time
      required:
        - record_id
        - reason
        - scope
        - suppressed_at
      description: A record you hold that is now suppressed, as the suppression.created webhook carries it.
    SuppressionRemoval:
      type: object
      properties:
        list_id:
          type: string
          pattern: ^sup_[0-7][0-9A-HJKMNP-TV-Z]{25}$
          description: "Format: a suppression list id: sup_ followed by a 26 character ULID in upper case Crockford base32, such as sup_01JB4H7Q2V7XN3M9R6T1Y5W8ZA."
          examples:
            - sup_01JB4H7Q2V7XN3M9R6T1Y5W8ZA
        removed:
          type: integer
          minimum: 0
      required:
        - list_id
        - removed
      description: Records one suppression list removed.
    SuppressionUpload:
      type: object
      properties:
        list:
          $ref: "#/components/schemas/SuppressionList"
        rows_read:
          type: integer
          minimum: 0
        rows_valid:
          type: integer
          minimum: 0
        rows_skipped:
          type: array
          items:
            type: object
            properties:
              row:
                type: integer
                minimum: 1
              reason:
                type: string
            required:
              - row
              - reason
      required:
        - list
        - rows_read
        - rows_valid
        - rows_skipped
      description: "A saved list and what the upload read: rows read, valid and skipped with reasons."
    TitleExpansion:
      type: object
      properties:
        dataset_version:
          type: string
          pattern: ^[0-9]{4}-W(0[1-9]|[1-4][0-9]|5[0-3])$
          description: The immutable weekly build a count is bound to, such as 2026-W39. The last 52 stay queryable.
          examples:
            - 2026-W39
        mode:
          type: string
          enum:
            - contains
            - starts_with
            - exact
          description: contains (the default) matches titles containing a term, starts_with matches titles beginning with it, exact takes verbatim titles with no expansion.
        expand:
          type: array
          items:
            type: string
            enum:
              - plural_singular
              - department_synonyms
              - ampersand_and
              - abbreviations
              - permutations
        terms:
          type: array
          items:
            $ref: "#/components/schemas/ExpandedTerm"
        total_contacts:
          $ref: "#/components/schemas/CellCount"
        bundle:
          anyOf:
            - type: object
              properties:
                id:
                  type: string
                  enum:
                    - sdm
                    - manager_and_above
                    - owner_managed
                label:
                  type: string
                status:
                  type: string
                  enum:
                    - confirmed
                    - proposed
                    - to_confirm
                resolves_to:
                  $ref: "#/components/schemas/WhereNode"
              required:
                - id
                - label
                - status
                - resolves_to
            - type: "null"
        filter:
          anyOf:
            - $ref: "#/components/schemas/Filter"
            - type: "null"
          description: A ready matches filter for the terms, or null for a bundle.
        disclosure:
          $ref: "#/components/schemas/Disclosure"
      required:
        - dataset_version
        - mode
        - expand
        - terms
        - total_contacts
        - bundle
        - filter
        - disclosure
      description: Every expanded title with a count, or what a bundle resolves to. Nothing is resolved silently.
    TitleExpansionRequest:
      type: object
      properties:
        terms:
          description: Title keywords to expand, such as marketing manager.
          minItems: 1
          maxItems: 50
          type: array
          items:
            type: string
            minLength: 1
            maxLength: 200
        bundle:
          description: "A title bundle to show: sdm, manager_and_above or owner_managed."
          type: string
          enum:
            - sdm
            - manager_and_above
            - owner_managed
        mode:
          type: string
          enum:
            - contains
            - starts_with
            - exact
          description: contains (the default) matches titles containing a term, starts_with matches titles beginning with it, exact takes verbatim titles with no expansion.
        expand:
          description: Expansion sets to apply, each once. Left out, the four default sets apply.
          uniqueItems: true
          type: array
          items:
            type: string
            enum:
              - plural_singular
              - department_synonyms
              - ampersand_and
              - abbreviations
              - permutations
        dataset:
          type: string
          enum:
            - uk_contacts
            - ie_contacts
            - global_contacts
          description: The dataset an audience draws from. uk_contacts is live; ie_contacts and global_contacts are listed but not yet served.
      additionalProperties: false
      description: "Keywords or a bundle to expand into an editable title list: exactly one of terms or bundle."
      oneOf:
        - type: object
          properties:
            terms: true
          required:
            - terms
        - type: object
          properties:
            bundle: true
          required:
            - bundle
    TpsScreen:
      type: object
      properties:
        screened_at:
          anyOf:
            - type: string
              format: date
            - type: "null"
          description: The version's TPS and CTPS screen date (quality.tps_ctps_screened_at); null when no number in it is screened.
        age_days:
          anyOf:
            - type: integer
              minimum: 0
            - type: "null"
          description: Days from the screen date to today; null when no number is screened.
        status:
          type: string
          enum:
            - ok
            - warning
            - expired
          description: ok up to 21 days, warning from 22 to 28 days, and expired after 28 days, when counts, samples, quotes and orders with telephone in play are refused with tps-screen-expired. ok when no number is screened.
      required:
        - screened_at
        - age_days
        - status
      description: The TPS and CTPS screen of the current dataset version, on today's date.
    TrustDocument:
      type: object
      properties:
        updated_at:
          type: string
          format: date-time
        company:
          type: object
          properties:
            legal_name:
              type: string
            company_number:
              type: string
            trading_as:
              type: string
          required:
            - legal_name
            - company_number
            - trading_as
        ico_registration:
          type: object
          properties:
            number:
              type: string
            expires_on:
              type: string
              format: date
            register_url:
              type: string
              format: uri
          required:
            - number
            - expires_on
            - register_url
        lawful_basis:
          type: object
          properties:
            basis:
              type: string
              enum:
                - legitimate_interests_art6_1_f
            lia_ref:
              type: string
            lia_url:
              type: string
              format: uri
          required:
            - basis
            - lia_ref
            - lia_url
        privacy_notice:
          type: object
          properties:
            version:
              type: string
            url:
              type: string
              format: uri
            versions:
              type: array
              items:
                $ref: "#/components/schemas/NoticeVersion"
              description: Every version of the Article 14 notice, newest first.
          required:
            - version
            - url
            - versions
          description: The Article 14 privacy notice for contacts, with its history.
        residency:
          type: object
          properties:
            statement:
              type: string
            version:
              type: string
          required:
            - statement
            - version
        sub_processors:
          type: object
          properties:
            version:
              type: string
            notice_days:
              type: integer
              exclusiveMinimum: 0
            notice_channels:
              type: array
              items:
                type: string
                enum:
                  - email
                  - webhook
              description: "How customers hear of a change 30 days ahead: by email and by webhook."
            list:
              type: array
              items:
                $ref: "#/components/schemas/SubProcessor"
            changes:
              type: array
              items:
                $ref: "#/components/schemas/SubProcessorChange"
              description: The change log, newest first.
          required:
            - version
            - notice_days
            - notice_channels
            - list
            - changes
        retention:
          type: array
          items:
            type: object
            properties:
              category:
                type: string
              period:
                type: string
              status:
                type: string
                enum:
                  - confirmed
                  - to_confirm
            required:
              - category
              - period
              - status
        complaints:
          type: object
          properties:
            url:
              type: string
              format: uri
              description: The complaints form.
            email:
              anyOf:
                - type: string
                  format: email
                - type: "null"
              description: The complaints address; null until published.
            acknowledgement_days:
              type: integer
              exclusiveMinimum: 0
          required:
            - url
            - email
            - acknowledgement_days
        data_protection_lead:
          anyOf:
            - type: object
              properties:
                name:
                  type: string
                email:
                  type: string
                  format: email
              required:
                - name
                - email
            - type: "null"
          description: Named person and email; null until confirmed.
        datasets:
          type: array
          items:
            $ref: "#/components/schemas/Dataset"
          description: Every dataset with owner, source, licence, refresh, residency, regulator, warranty and credits per record.
        security:
          type: object
          properties:
            certifications:
              type: array
              items:
                type: object
                properties:
                  name:
                    type: string
                  status:
                    type: string
                    enum:
                      - held
                      - target
                  date:
                    type: string
                    description: Year and month, such as 2026-10.
                required:
                  - name
                  - status
                  - date
            encryption:
              type: object
              properties:
                at_rest:
                  type: string
                  description: Such as AES-256.
                in_transit_minimum:
                  type: string
                  description: Such as TLS 1.2.
                key_rotation:
                  type: string
                  description: Such as KMS managed key rotation.
              required:
                - at_rest
                - in_transit_minimum
                - key_rotation
              description: How data is encrypted, as the trust centre publishes it.
            data_flow_diagram_url:
              anyOf:
                - type: string
                  format: uri
                - type: "null"
              description: The UK data flow diagram; null until published.
            pen_test_summary_url:
              anyOf:
                - type: string
                  format: uri
                - type: "null"
              description: The latest penetration test summary; null until one is published.
            caiq_url:
              anyOf:
                - type: string
                  format: uri
                - type: "null"
              description: CAIQ v4 answers; null until published.
            status_page:
              type: string
              format: uri
            security_txt:
              type: string
              format: uri
          required:
            - certifications
            - encryption
            - data_flow_diagram_url
            - pen_test_summary_url
            - caiq_url
            - status_page
            - security_txt
      required:
        - updated_at
        - company
        - ico_registration
        - lawful_basis
        - privacy_notice
        - residency
        - sub_processors
        - retention
        - complaints
        - data_protection_lead
        - datasets
        - security
      description: The trust page as data, mirrored at https://mydatahub.emailmovers.com/trust.json and the mydatahub://compliance resource.
    ValidationReportLinks:
      type: object
      properties:
        pdf_url:
          type: string
          format: uri
        json_url:
          type: string
          format: uri
      required:
        - pdf_url
        - json_url
      description: "The per-order validation report: dataset version, status distribution, verification age, expected bounce band, TPS screen date, provider locations and the guarantee window."
    VerificationBadge:
      oneOf:
        - $ref: "#/components/schemas/VerifiedBadge"
        - $ref: "#/components/schemas/OtherBadge"
      description: A badge on a sample row. Every verified has a date.
      discriminator:
        propertyName: badge
        mapping:
          verified: "#/components/schemas/VerifiedBadge"
          re_checking: "#/components/schemas/OtherBadge"
          unverified: "#/components/schemas/OtherBadge"
          suppressed: "#/components/schemas/OtherBadge"
    VerifiedBadge:
      type: object
      properties:
        badge:
          type: string
          const: verified
        date:
          type: string
          format: date
          description: The verification date. Every verified has one.
        label:
          type: string
          description: Such as Verified 12 September 2026.
      required:
        - badge
        - date
        - label
      description: Verified, with the date it was verified.
    Warranty:
      type: object
      properties:
        policy:
          type: string
          enum:
            - replace_over_threshold_hard_bounce
            - replace_any_hard_bounce_30d
          description: "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."
        threshold_percent:
          description: "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."
          type: number
          minimum: 0
          maximum: 100
        url:
          type: string
          format: uri
      required:
        - policy
        - url
      description: The bounce warranty that covers delivered records, with the threshold it was issued under.
      allOf:
        - if:
            type: object
            properties:
              policy:
                const: replace_over_threshold_hard_bounce
            required:
              - policy
          then:
            type: object
            properties:
              threshold_percent: true
            required:
              - threshold_percent
          else:
            type: object
            not:
              type: object
              properties:
                threshold_percent: true
              required:
                - threshold_percent
    WarrantyReplacementEvent:
      type: object
      properties:
        id:
          type: string
          pattern: ^evt_[0-7][0-9A-HJKMNP-TV-Z]{25}$
          description: Also sent as the webhook-id header; your idempotency key.
          examples:
            - evt_01JB4NB7T1Y5W8ZAK8Q2V7XN3M
        type:
          type: string
          const: warranty.replacement_issued
        timestamp:
          type: string
          format: date-time
        api_version:
          type: string
          pattern: ^[0-9]{4}-(0[1-9]|1[0-2])-(0[1-9]|[12][0-9]|3[01])$
          description: A dated MyDataHub-Version, such as 2027-01-15.
        data:
          type: object
          properties:
            order_id:
              type: string
              pattern: ^ord_[0-7][0-9A-HJKMNP-TV-Z]{25}$
              description: "Format: an order id: ord_ followed by a 26 character ULID in upper case Crockford base32, such as ord_01JB4N9S3W2X6Y7Z8A1B2C3D4E."
              examples:
                - ord_01JB4N9S3W2X6Y7Z8A1B2C3D4E
            retired_record_id:
              type: string
              pattern: ^contact_[0-7][0-9A-HJKMNP-TV-Z]{25}$
              description: "Format: a contact id: contact_ followed by a 26 character ULID in upper case Crockford base32, such as contact_01JA9X2V7XN3M9R6T1Y5W8ZAK8."
              examples:
                - contact_01JA9X2V7XN3M9R6T1Y5W8ZAK8
            replacement_record_id:
              anyOf:
                - type: string
                  pattern: ^contact_[0-7][0-9A-HJKMNP-TV-Z]{25}$
                  description: "Format: a contact id: contact_ followed by a 26 character ULID in upper case Crockford base32, such as contact_01JA9X2V7XN3M9R6T1Y5W8ZAK8."
                  examples:
                    - contact_01JA9X2V7XN3M9R6T1Y5W8ZAK8
                - type: "null"
            reason:
              type: string
              enum:
                - hard_bounce
                - job_change
            credits_refunded:
              type: number
              minimum: 0
              description: "Credits, to one decimal place: whole at 1 credit a record, fractional only where a rule is (0.1 company-only, 1.8 outright, both proposed)."
          required:
            - order_id
            - retired_record_id
            - replacement_record_id
            - reason
            - credits_refunded
      required:
        - id
        - type
        - timestamp
        - api_version
        - data
      description: A bounced or job-changed record was replaced, or re-credited.
    WebhookEndpoint:
      type: object
      properties:
        id:
          type: string
          pattern: ^whk_[0-7][0-9A-HJKMNP-TV-Z]{25}$
          description: "Format: a webhook endpoint id: whk_ followed by a 26 character ULID in upper case Crockford base32, such as whk_01JB3Y8Q2V7XN3M9R6T1Y5W8ZA."
          examples:
            - whk_01JB3Y8Q2V7XN3M9R6T1Y5W8ZA
        url:
          type: string
          format: uri
        events:
          type: array
          items:
            type: string
            enum:
              - order.ready
              - order.failed
              - audience.refreshed
              - credits.low
              - credits.topped_up
              - warranty.replacement_issued
              - suppression.created
              - push.completed
              - push.partial
              - push.failed
              - connection.expired
        api_version:
          type: string
          pattern: ^[0-9]{4}-(0[1-9]|1[0-2])-(0[1-9]|[12][0-9]|3[01])$
          description: Pinned when the endpoint is created.
        status:
          type: string
          enum:
            - active
            - disabled
        description:
          type:
            - string
            - "null"
        created_at:
          type: string
          format: date-time
      required:
        - id
        - url
        - events
        - api_version
        - status
        - description
        - created_at
      description: Where to deliver events, and which ones.
    WebhookEndpointCreated:
      type: object
      properties:
        endpoint:
          $ref: "#/components/schemas/WebhookEndpoint"
        secret:
          type: string
          pattern: ^whsec_[A-Za-z0-9+/]{32,86}={0,2}$
          description: The signing secret (whsec_), shown once. Rotate it from the app.
      required:
        - endpoint
        - secret
      description: A new endpoint and its signing secret, shown once.
    WebhookEndpointList:
      type: object
      properties:
        data:
          type: array
          items:
            $ref: "#/components/schemas/WebhookEndpoint"
        has_more:
          type: boolean
          description: Whether another page follows.
        next_cursor:
          anyOf:
            - type: string
              minLength: 1
              maxLength: 512
              description: An opaque cursor from next_cursor. Pass it back unchanged.
            - type: "null"
          description: Pass as cursor to fetch the next page; null on the last page.
      required:
        - data
        - has_more
        - next_cursor
      description: A page of webhook endpoints, newest first.
    WebhookEndpointRequest:
      type: object
      properties:
        url:
          type: string
          format: uri
          pattern: ^https://
        events:
          minItems: 1
          type: array
          items:
            type: string
            enum:
              - order.ready
              - order.failed
              - audience.refreshed
              - credits.low
              - credits.topped_up
              - warranty.replacement_issued
              - suppression.created
              - push.completed
              - push.partial
              - push.failed
              - connection.expired
        description:
          type: string
          maxLength: 200
      required:
        - url
        - events
      additionalProperties: false
      description: A new endpoint, pinned to the MyDataHub-Version of the request.
    WebhookEvent:
      oneOf:
        - $ref: "#/components/schemas/OrderReadyEvent"
        - $ref: "#/components/schemas/OrderFailedEvent"
        - $ref: "#/components/schemas/AudienceRefreshedEvent"
        - $ref: "#/components/schemas/CreditsLowEvent"
        - $ref: "#/components/schemas/CreditsToppedUpEvent"
        - $ref: "#/components/schemas/WarrantyReplacementEvent"
        - $ref: "#/components/schemas/SuppressionCreatedEvent"
        - $ref: "#/components/schemas/PushCompletedEvent"
        - $ref: "#/components/schemas/PushPartialEvent"
        - $ref: "#/components/schemas/PushFailedEvent"
        - $ref: "#/components/schemas/ConnectionExpiredEvent"
      description: "A Standard Webhooks payload: {type, timestamp, data} plus the event id and the pinned api_version."
      discriminator:
        propertyName: type
        mapping:
          order.ready: "#/components/schemas/OrderReadyEvent"
          order.failed: "#/components/schemas/OrderFailedEvent"
          audience.refreshed: "#/components/schemas/AudienceRefreshedEvent"
          credits.low: "#/components/schemas/CreditsLowEvent"
          credits.topped_up: "#/components/schemas/CreditsToppedUpEvent"
          warranty.replacement_issued: "#/components/schemas/WarrantyReplacementEvent"
          suppression.created: "#/components/schemas/SuppressionCreatedEvent"
          push.completed: "#/components/schemas/PushCompletedEvent"
          push.partial: "#/components/schemas/PushPartialEvent"
          push.failed: "#/components/schemas/PushFailedEvent"
          connection.expired: "#/components/schemas/ConnectionExpiredEvent"
    WebhookTest:
      type: object
      properties:
        event_id:
          type: string
          pattern: ^evt_[0-7][0-9A-HJKMNP-TV-Z]{25}$
          description: "Format: an event id: evt_ followed by a 26 character ULID in upper case Crockford base32, such as evt_01JB4NB7T1Y5W8ZAK8Q2V7XN3M."
          examples:
            - evt_01JB4NB7T1Y5W8ZAK8Q2V7XN3M
        delivered:
          type: boolean
        status_code:
          type:
            - integer
            - "null"
        duration_ms:
          anyOf:
            - type: integer
              minimum: 0
            - type: "null"
      required:
        - event_id
        - delivered
        - status_code
        - duration_ms
      description: The result of sending a test event to an endpoint.
    WhereNode:
      anyOf:
        - $ref: "#/components/schemas/AllGroup"
        - $ref: "#/components/schemas/AnyGroup"
        - $ref: "#/components/schemas/NotGroup"
        - $ref: "#/components/schemas/BundleNode"
        - $ref: "#/components/schemas/Filter"
      description: A group (all, any, not), a title bundle or a filter. Groups nest up to 32 deep, and a where tree holds up to 1,000 filters, groups and bundles.
    WhyItMatches:
      type: object
      properties:
        pointer:
          type: string
          description: JSON pointer to the filter in the audience.
        field:
          type: string
        value:
          description: The record's actual value.
          anyOf:
            - type: string
            - type: number
            - type: boolean
        explanation:
          type: string
          description: Such as Marketing Director matched marketing manager through expansion.
      required:
        - pointer
        - field
        - value
        - explanation
      description: One predicate the record satisfies, with its value.
    WithinRadiusFilter:
      type: object
      properties:
        field:
          type: string
          enum:
            - site.location
        op:
          type: string
          const: within_radius
        centre:
          $ref: "#/components/schemas/RadiusCentre"
        radius_miles:
          type: number
          minimum: 1
          maximum: 100
          description: Radius in miles, 1 to 100. Under 5 miles needs an account.
      required:
        - field
        - op
        - centre
        - radius_miles
      additionalProperties: false
      description: Sites within radius_miles of the centre. The count reports the centroid used and the postcodes matched.
      examples:
        - field: site.location
          op: within_radius
          centre:
            place: Leeds
          radius_miles: 20
  responses:
    BadRequest:
      description: The request could not be read, or an Idempotency-Key is missing.
      headers:
        RateLimit:
          $ref: "#/components/headers/RateLimit"
        RateLimit-Policy:
          $ref: "#/components/headers/RateLimit-Policy"
      content:
        application/problem+json:
          schema:
            $ref: "#/components/schemas/Problem"
          examples:
            default:
              summary: Example
              value:
                type: https://emailmovers.com/mydatahub/docs/errors/idempotency-key-required
                title: An Idempotency-Key header is required
                status: 400
                code: idempotency-key-required
                detail: POST /v1/orders needs an Idempotency-Key header.
                next_step: Send an Idempotency-Key header (a V4 UUID is a good choice) and reuse it on every retry.
                instance: /v1/orders
    Unauthorized:
      description: 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).
      headers:
        RateLimit:
          $ref: "#/components/headers/RateLimit"
        RateLimit-Policy:
          $ref: "#/components/headers/RateLimit-Policy"
        WWW-Authenticate:
          $ref: "#/components/headers/WWW-Authenticate"
      content:
        application/problem+json:
          schema:
            $ref: "#/components/schemas/Problem"
          examples:
            default:
              summary: Example
              value:
                type: https://emailmovers.com/mydatahub/docs/errors/authentication-required
                title: Sign in or send an API key
                status: 401
                code: authentication-required
                detail: GET /v1/orders needs a token with orders:read.
                next_step: "Send Authorization: Bearer with an mdh_ API key. Sign-in through https://mydatahub.emailmovers.com/id is planned."
                instance: /v1/orders
    InsufficientCredits:
      description: The balance does not cover the machine quote, which is carried here. Nothing was charged; top up on mydatahub.emailmovers.com and retry with the same quote_id.
      headers:
        RateLimit:
          $ref: "#/components/headers/RateLimit"
        RateLimit-Policy:
          $ref: "#/components/headers/RateLimit-Policy"
      content:
        application/problem+json:
          schema:
            $ref: "#/components/schemas/InsufficientCreditsProblem"
          examples:
            default:
              summary: Example
              value:
                type: https://emailmovers.com/mydatahub/docs/errors/insufficient-credits
                title: Not enough credits
                status: 402
                code: insufficient-credits
                detail: The machine quote needs 5,000 credits and the balance is 1,200.
                next_step: Top up at the top_up_url, then retry with the same machine quote_id (or the same enrichment request).
                instance: /v1/orders
                quote_id: qte_01JB4M2R6T1Y5W8ZAK8Q2V7XN3
                quote:
                  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
                credits_required: 5000
                credits_available: 1200
                top_up_url: https://mydatahub.emailmovers.com/billing/top-up?quote=qte_01JB4M2R6T1Y5W8ZAK8Q2V7XN3
    Forbidden:
      description: The token lacks a scope (insufficient-scope, with required_scope and a WWW-Authenticate step-up challenge); no person confirmed the spend and no agent key covers it (confirmation-required, with an approval_url); an agent key's per-order cap or monthly budget does not cover it (agent-budget-exceeded); an account made by self-serve sign-up has not yet confirmed its business name and that it is a UK business, which its first order needs (business-details-required); the order needs the Data Licence and Sharing Agreement accepted at the version in force (licence-acceptance-required); or a suppression upload comes before the organisation has accepted the Article 28 data processing agreement in force (dpa-acceptance-required). Nothing is charged.
      headers:
        RateLimit:
          $ref: "#/components/headers/RateLimit"
        RateLimit-Policy:
          $ref: "#/components/headers/RateLimit-Policy"
        WWW-Authenticate:
          $ref: "#/components/headers/WWW-Authenticate-InsufficientScope"
      content:
        application/problem+json:
          schema:
            anyOf:
              - $ref: "#/components/schemas/AgentBudgetProblem"
              - $ref: "#/components/schemas/ConfirmationRequiredProblem"
              - $ref: "#/components/schemas/Problem"
          examples:
            confirmation:
              summary: A person must confirm
              value:
                type: https://emailmovers.com/mydatahub/docs/errors/confirmation-required
                title: A person must confirm this spend
                status: 403
                code: confirmation-required
                detail: A standard API key cannot spend on its own. Nothing was charged; keep the machine quote and ask the user to approve it.
                next_step: 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.
                instance: /v1/orders
                action: order
                quote_id: qte_01JB4M2R6T1Y5W8ZAK8Q2V7XN3
                approval_url: https://mydatahub.emailmovers.com/quotes/qte_01JB4M2R6T1Y5W8ZAK8Q2V7XN3/approve
                expires_at: "2026-09-27T10:14:00Z"
            budget:
              summary: Agent budget exceeded
              value:
                type: https://emailmovers.com/mydatahub/docs/errors/agent-budget-exceeded
                title: The agent key budget does not cover this spend
                status: 403
                code: agent-budget-exceeded
                detail: The machine quote needs 5,000 credits; this key may spend 500 an order and has 600 left this month.
                next_step: Keep the machine quote_id and ask the key owner to approve it at approval_url, or top up at top_up_url.
                instance: /v1/orders
                budget:
                  monthly_credits: 1000
                  spent: 400
                  remaining: 600
                  resets_at: "2026-10-01T00:00:00Z"
                per_order_max_credits: 500
                quote_id: qte_01JB4M2R6T1Y5W8ZAK8Q2V7XN3
                credits_required: 5000
                approval_url: https://mydatahub.emailmovers.com/quotes/qte_01JB4M2R6T1Y5W8ZAK8Q2V7XN3/approve
                top_up_url: https://mydatahub.emailmovers.com/billing/top-up
            scope:
              summary: Missing scope
              value:
                type: https://emailmovers.com/mydatahub/docs/errors/insufficient-scope
                title: The token lacks a scope
                status: 403
                code: insufficient-scope
                detail: Creating an order needs the orders:write scope.
                next_step: Ask the user to approve the scope named in required_scope, then retry.
                instance: /v1/orders
                required_scope: orders:write
    NotFound:
      description: No such object for this organisation.
      headers:
        RateLimit:
          $ref: "#/components/headers/RateLimit"
        RateLimit-Policy:
          $ref: "#/components/headers/RateLimit-Policy"
      content:
        application/problem+json:
          schema:
            $ref: "#/components/schemas/Problem"
          examples:
            default:
              summary: Example
              value:
                type: https://emailmovers.com/mydatahub/docs/errors/not-found
                title: Not found
                status: 404
                code: not-found
                detail: No order ord_01JB4N9S3W2X6Y7Z8A1B2C3D4E in this organisation.
                next_step: Check the id; ids are prefixed, such as ord_ for orders and aud_ for audiences.
                instance: /v1/orders/ord_01JB4N9S3W2X6Y7Z8A1B2C3D4E
    Conflict:
      description: An Idempotency-Key reused with different parameters, an expired machine quote, or an object in the wrong state.
      headers:
        RateLimit:
          $ref: "#/components/headers/RateLimit"
        RateLimit-Policy:
          $ref: "#/components/headers/RateLimit-Policy"
      content:
        application/problem+json:
          schema:
            $ref: "#/components/schemas/Problem"
          examples:
            default:
              summary: Example
              value:
                type: https://emailmovers.com/mydatahub/docs/errors/quote-expired
                title: The machine quote has expired
                status: 409
                code: quote-expired
                detail: Machine quote qte_01JB4M2R6T1Y5W8ZAK8Q2V7XN3 expired at 2026-09-27T10:14:00Z.
                next_step: Call quote_order (POST /v1/quotes) again; a machine quote is valid for 24 hours.
                instance: /v1/orders
    ValidationFailed:
      description: The request is not valid. errors lists every problem with a JSON pointer and did_you_mean.
      headers:
        RateLimit:
          $ref: "#/components/headers/RateLimit"
        RateLimit-Policy:
          $ref: "#/components/headers/RateLimit-Policy"
      content:
        application/problem+json:
          schema:
            $ref: "#/components/schemas/Problem"
          examples:
            default:
              summary: Example
              value:
                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.
                errors:
                  - pointer: /audience/where/all/0/field
                    code: invalid_value
                    message: Unknown field company.employees.
                    did_you_mean:
                      - company.employee_band
    RateLimited:
      description: Too many requests. Wait for Retry-After.
      headers:
        RateLimit:
          $ref: "#/components/headers/RateLimit"
        RateLimit-Policy:
          $ref: "#/components/headers/RateLimit-Policy"
        Retry-After:
          $ref: "#/components/headers/Retry-After"
      content:
        application/problem+json:
          schema:
            $ref: "#/components/schemas/Problem"
          examples:
            default:
              summary: Example
              value:
                type: https://emailmovers.com/mydatahub/docs/errors/rate-limited
                title: Too many requests
                status: 429
                code: rate-limited
                detail: 60 counts an hour per IP for anonymous callers; the next resets in 42 seconds.
                next_step: Wait for the seconds in Retry-After, then retry.
                instance: /v1/counts
                retry_after_seconds: 42
    ServerError:
      description: 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.
      headers:
        RateLimit:
          $ref: "#/components/headers/RateLimit"
        RateLimit-Policy:
          $ref: "#/components/headers/RateLimit-Policy"
        Retry-After:
          $ref: "#/components/headers/Retry-After"
      content:
        application/problem+json:
          schema:
            $ref: "#/components/schemas/Problem"
          examples:
            default:
              summary: Example
              value:
                type: https://emailmovers.com/mydatahub/docs/errors/service-unavailable
                title: The service is briefly unavailable
                status: 503
                code: service-unavailable
                detail: The serving store is failing over; counts resume within a minute.
                next_step: Wait for the seconds in Retry-After, then retry with the same Idempotency-Key. The status page shows incidents.
                instance: /v1/counts
                retry_after_seconds: 30
  parameters:
    MyDataHubVersion:
      name: MyDataHub-Version
      in: header
      required: false
      description: 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.
      schema:
        type: string
        pattern: ^[0-9]{4}-[0-9]{2}-[0-9]{2}$
      example: "2027-01-15"
    IdempotencyKey:
      name: Idempotency-Key
      in: header
      required: false
      description: "Makes the request safe to retry: the same key returns the stored response. Keys are kept for the life of the organisation."
      schema:
        type: string
        minLength: 1
        maxLength: 255
      example: 6f1c3b2a-9d4e-4f8a-b1c2-7e5d9a0f3c21
    IdempotencyKeyRequired:
      name: Idempotency-Key
      in: header
      required: true
      description: Required. A retry with the same key never charges twice; a different request with the same key returns 409. Keys are kept for the life of the organisation.
      schema:
        type: string
        minLength: 1
        maxLength: 255
      example: 6f1c3b2a-9d4e-4f8a-b1c2-7e5d9a0f3c21
  headers:
    RateLimit:
      description: "IETF RateLimit field: one item for each window that applies (counts-hourly, counts-daily, counts-per-minute or requests), each with the remaining quota (r) and the seconds to reset (t)."
      schema:
        type: string
      example: '"counts-hourly";r=59;t=3522, "counts-daily";r=599;t=50322, "requests";r=119;t=42'
    RateLimit-Policy:
      description: "IETF RateLimit-Policy field: one item for each window that applies, each with the quota (q) and the window in seconds (w). Anonymous callers get 60 counts an hour and 600 a day per IP and fingerprint."
      schema:
        type: string
      example: '"counts-hourly";q=60;w=3600, "counts-daily";q=600;w=86400, "requests";q=120;w=60'
    Retry-After:
      description: Seconds to wait before retrying.
      schema:
        type: integer
        minimum: 0
      example: 42
    ETag:
      description: "For a count, the count id in quotes: the same definition gives the same ETag."
      schema:
        type: string
      example: '"cnt_7K2PQ4MAXN3R6TBY5WDZ"'
    Location:
      description: "Where the new resource, or the signed file, lives: a path on https://mydatahub.emailmovers.com, resolved against the origin."
      schema:
        type: string
        format: uri-reference
      example: /v1/orders/ord_01JB4N9S3W2X6Y7Z8A1B2C3D4E
    WWW-Authenticate:
      description: The Bearer challenge, naming the protected resource metadata and the scopes to request.
      schema:
        type: string
      example: Bearer resource_metadata="https://mydatahub.emailmovers.com/.well-known/oauth-protected-resource/v1", scope="counts:read previews:read audiences:read"
    WWW-Authenticate-InsufficientScope:
      description: 'The step-up challenge on 403 insufficient-scope (RFC 6750): error="insufficient_scope" with the scope to request, which MCP and OAuth clients read to ask the user, such as orders:write on the first create_order.'
      schema:
        type: string
      example: Bearer error="insufficient_scope", scope="orders:write", resource_metadata="https://mydatahub.emailmovers.com/.well-known/oauth-protected-resource/v1"
  securitySchemes:
    OAuth2:
      type: oauth2
      description: "OAuth 2.1 at https://mydatahub.emailmovers.com/id, the one issuer for the app, REST and MCP: authorization code with PKCE (S256 only), rotating refresh tokens, 60 minute JWT access tokens with aud set to https://mydatahub.emailmovers.com/v1. Scopes are stepped up: the first challenge asks for counts:read previews:read audiences:read, and the first spend answers 403 insufficient_scope naming orders:write."
      flows:
        authorizationCode:
          authorizationUrl: https://mydatahub.emailmovers.com/id/authorize
          tokenUrl: https://mydatahub.emailmovers.com/id/token
          refreshUrl: https://mydatahub.emailmovers.com/id/token
          scopes:
            account:read: Read the balance, plan, prices, rate limits, usage and key budgets.
            account:write: Set the business name and confirm that it is a UK business (PATCH /v1/account). A signed-in owner only; admin covers it too.
            counts:read: Run counts and breakdowns with the account's exact figures and New to you.
            previews:read: Draw masked samples.
            audiences:read: Read saved audiences.
            audiences:write: Save, update, refresh and delete audiences.
            quotes:read: Create and read machine quotes with the account's balance.
            orders:read: Read orders, compliance receipts and CRM pushes.
            orders:write: "Spend credits: create and cancel orders."
            records:read: Fetch and download delivered records under licence.
            enrich:write: "Spend credits: enrich a known contact, charged only on a confident match."
            suppressions:read: Read suppression lists and check records before a send.
            suppressions:write: Upload suppression lists and report objections.
            pushes:write: Connect CRMs, check overlap and push delivered records.
            webhooks:write: Manage webhook endpoints and read the event log.
            samples:request: Ask for a sample of a counted audience, sent by email to a confirmed work address. Never returns records.
            admin: Manage API keys, agent keys and the plan, including cancellation. Keys are made and changed by signed-in people only, and an agent key never holds admin.
    ApiKey:
      type: http
      scheme: bearer
      bearerFormat: mdh_live_ or mdh_test_ API key
      description: "An API key or agent key as Authorization: Bearer, never a custom header or query string. mdh_test_ keys return synthetic data and never charge. Keys carry scopes like OAuth tokens. A standard key never spends on its own: a spend needs a person's confirmation or an agent key within its limits."
