EmailmoversMyDataHub docs
Browse the docs

Migrate and change

Migration guide

Move from today's 13 MCP tools and the RunExtract endpoint to v2. A table maps every old call to its replacement, with worked before and after examples.

Updated 16 October 2026View as Markdown

Every one of today's 13 MCP tools has a replacement among the 22 new ones, and the legacy download endpoint has one too. The plan is a 12 month window from the v2 launch, with 3 emailed notices, in which today's tools, keys and installs keep working while you move. Installs at today's MCP URL, https://mcp.mydatahub.uk, are planned to keep working: it passes MCP through to the new server at https://mydatahub.emailmovers.com/mcp for at least 12 months, with no redirect, and credit balances and saved lists are planned to carry over.

  • The X-Api-Key header and POST /api/RunExtract. The v2 API does not accept the header, has no such route and sends no Deprecation or Sunset header.
  • Tokens from today's OAuth issuer. The v2 API refuses any bearer token that is not an mdh_ key.
  • The redirect from agents.mydatahub.uk to this site, page for page.

Moving traffic to v2 without them would break today's integrations and installs. The launch has to leave today's endpoints where they are until they exist (to confirm).

What changes

TodayAfter migration, as planned
13 MCP tools that hold a session on the server22 tools where every call carries its own audience
Numeric structureId valuesField names such as company.employee_band, from list_filters
incEx codes 0, 1 and 2in to include, and in under not to exclude
create_data_extract with confirmed truequote_order, then create_order with a person's confirmation or an agent key
POST /api/RunExtract with X-Api-KeyGET /v1/orders/{id}/records with Authorization: Bearer
Credit balanceAn opening ledger entry, one for one, with licence dates unchanged
Saved listsAudiences with aud_ ids, in the same organisation
29 export bundles, five of them testsNamed field sets, 1 credit a record each. The test bundles are removed
agents.mydatahub.ukThis site, with a permanent redirect page for page (not built)

The plan is for the old tools, the X-Api-Key header and POST /api/RunExtract to stay available for 12 months from the v2 launch date, with 3 emailed notices, and then to return 410, with Deprecation and Sunset headers on legacy download responses (RFC 9745 and RFC 8594). Only the old MCP tools are built so far. The v2 API has no X-Api-Key handling, no /api/RunExtract route and no deprecation headers yet, so those parts of the plan are to confirm.

The 13 tools

The old tools are listed after the 22 new ones, with "Deprecated" in their titles and the replacement named in each description. preview_records is the thirteenth: the new tool keeps the name and replaces it in place, so a name appears once in tools/list. The last column says what each old tool does on the v2 server now.

Today's toolReplaced byWhat the replacement doesDuring the window
available_selection_categorieslist_filtersIt returns every field by name, such as company.employee_band, with its type, operators and value count.Runs list_filters and answers with its result.
available_selection_valuessearch_filter_valuesIt takes the field name and search text and returns codes with contact and company counts.Runs search_filter_values. A structureId with no field, or an offset above 0, is refused with a next step.
search_discrete_valuessearch_filter_valuesIt returns codes with counts and did_you_mean for near misses.Runs search_filter_values with the first value as the search text. A structureId with no field is refused with a next step.
create_searchcompile_audience, then count_audiencecount_audience takes the whole audience definition in one call, so there is no session state to build up, and compile_audience turns a brief into that definition.Counts the filters you send with count_audience. A range, or a structureId with no field, is refused with a next step. Nothing is kept for later calls.
clear_searchcount_audienceThere is nothing to clear: every count_audience call carries its own audience.Succeeds and does nothing, because there is no session to clear.
get_countcount_audienceIt returns Total, New to you and Emailable with breakdowns, quality, the compliance summary, a price estimate and a count id.Answers an error that names count_audience, because the filters it read were held in a session that no longer exists.
preview_recordspreview_recordsThe new preview_records keeps the name and takes an audience or a count_id instead; a call in the old form fails validation with a next_step naming count_audience.Not listed under its old form, because the new tool has the same name. A call in the old form fails validation with a next step that names count_audience.
save_selectionssave_audienceIt saves an audience definition shared across the organisation; saved lists carry over as audiences (aud_).Answers an error that names save_audience, because the filters it saved were held in a session that no longer exists.
get_saved_selectionslist_audiencesSaved lists carry over as audiences (aud_) in the same organisation, with their current counts.Runs list_audiences. An offset above 0 is refused: pass next_cursor instead.
create_data_extractquote_order, then create_orderThe flag is ignored, since a flag set by a model is never consent: the call makes a machine quote for the list, and the user confirms the spend in a form or on mydatahub.emailmovers.com, or an agent key covers it within its limits; otherwise nothing is charged and it returns the approval_url.Ignores confirmed. It needs the aud_ id of a saved audience, counts it, makes a machine quote and goes through the same confirmation as create_order. With neither a person's approval nor an agent key, nothing is charged and it returns the approval_url.
get_creditsget_accountIt returns the balance by source, the plan, per-action prices, the rate limit and any agent key budget; balances carry over one for one.Runs get_account and answers with its result.
get_packslist_field_setsThe old bundles become named field sets with their exact columns; the five test bundles are removed.Runs list_field_sets and answers with its result.
get_pack_detailslist_field_setslist_field_sets returns every field set with its exact columns and credits per record.Runs list_field_sets and adds that the old bundle ids became field set names.

What the old tools do meanwhile

During the window each deprecated tool answers as the last column above says. Nine run the tool that replaces them, three return an error that names the replacement, and clear_search succeeds and does nothing.

  • The answer is the new tool's result, with a "Deprecated:" line first and the replacement in _meta["uk.mydatahub/deprecated"].
  • A numeric structureId is mapped to a field name (the tables below), search becomes q, and limit is capped at 100. An offset above 0 has no equivalent: pass next_cursor instead.
  • create_search turns its filters into an audience and counts it. Includes become in, and excludes go under not. A range filter has no equivalent.
  • get_count and save_selections read and wrote filters held in a server session, and sessions no longer exist. They return an error that names count_audience and save_audience.
  • preview_records in the old form fails validation with a next_step that names count_audience.
  • clear_search succeeds and does nothing, because there is nothing to clear.
  • create_data_extract ignores confirmed. It makes a machine quote for the saved list and goes through the same confirmation as create_order. With neither a person's approval nor an agent key, nothing is charged and it returns the approval_url.

A retried create_data_extract for the same list, field set and volume reuses the live quote and the same idempotency key, so it finds the order it made and cannot buy a second batch. For another batch, call quote_order and then create_order with a new idempotency_key. A saved list's old numeric id has no mapping: call list_audiences to find its aud_ id.

Old ids to field names

These old structureId values each map to a field. Several old ids can stand for one field.

Old structureIdNew field
1site.region
2site.town
3site.postcode
5company.turnover_band
6company.employee_band
8email.type
9, 20company.business_class
10, 11, 12company.sic_2007
14, 18, 31contact.job_function
17company.fleet_band
21site.county
27company.type
28company.status
30contact.job_level_rank

These old ids have no field. A call that names one is refused with a next step.

Old structureIdWhat the old catalogue called itWhat to use now
15Company namelookup_company finds a company by its name.
34Emailenrich_contact looks a person up by email address.
23Email universeThe defaults, which exclude generic addresses and dissolved companies. email.exists and email.type are the filters.
22Phone universephone.exists.
24Mail universeNo equivalent field.
19TPSphone.tps_ctps_status. Whenever a phone is requested, numbers a screen found TPS or CTPS registered are left out by default; numbers not screened are kept and say so.
13TagNo equivalent field: tags were for internal use.
25, 26Matched jobs and Match prospectsNo equivalent field: they were internal matching references.
29ResearchNo equivalent field: an internal research universe.
36Internal quality flagNo equivalent field.

Old bundles to field sets

A telephone bundle becomes standard_telephone, the Full bundle becomes full, and every other bundle is delivered as standard. Check the columns with list_field_sets before you rely on one.

Old packIdWhat the old catalogue called itField set
545Test1standard, because the test bundles are removed
548Standardstandard
549Postalstandard
550Telephonestandard_telephone
551Postal and Telephonestandard_telephone
552Business Classstandard
553Job Levelstandard
554Job Functionstandard
555Fullfull
591Linked In Packstandard
599Job_Level_NEWstandard
607TESTstandard, because the test bundles are removed
613Eventbrite Packstandard, because the test bundles are removed
615Testtag Packstandard, because the test bundles are removed
618Samplestandard
622Boost + LinkedInstandard
762Phone Packstandard_telephone
783Website Onlystandard
822Data with Telstandard_telephone
824Telephone Packstandard_telephone
826Email Packstandard
837STANDARD PLUS LINKEDINstandard
843STANDARD PLUS WEBISTEstandard
848Skeletonstandard
887Company Numberstandard
906Agent Packstandard
93684013standard, because the test bundles are removed
968TEL_JOB_LEVELstandard_telephone
1036Linkedin PLUS TELstandard_telephone

Before and after

The values in the old calls are illustrative. Use list_filters and search_filter_values for the codes your account sees.

1. Counting without a session

Before, an agent built the count up on the server, one call at a time.

  1. available_selection_categories, to learn the numeric ids.
  2. available_selection_values with { "structureId": 6 }, to learn the codes.
  3. create_search with the filters to include or exclude.
  4. get_count, to read the number held in the session.
{
  "selections": [
    { "structure": 6, "fieldValue": "E", "incEx": 1 },
    { "structure": 28, "fieldValue": "dissolved", "incEx": 2 }
  ]
}

After, one call carries everything and returns the count, the breakdowns and an id.

{
  "name": "count_audience",
  "arguments": {
    "audience": {
      "schema": "mydatahub.audience/1",
      "dataset": "uk_contacts",
      "where": {
        "all": [
          { "field": "company.employee_band", "op": "gte", "value": "50-99" },
          { "not": { "field": "company.status", "op": "in", "values": ["dissolved"] } }
        ]
      }
    }
  }
}

There is nothing to clear and nothing to remember between calls. Dissolved companies are already excluded by default, and the exclusion above is only to show the shape. See The audience definition.

2. Finding a value

Before, available_selection_values took a numeric id and paged with offset.

{ "structureId": 10, "search": "manufact", "limit": 50 }

After, search_filter_values takes the field name and pages with a cursor. It returns codes with contact and company counts, and did_you_mean for near misses.

{ "dimension": "company.sic_2007", "q": "manufact", "limit": 50 }

3. Saving and listing

Before, save_selections saved whatever the session held, and get_saved_selections listed the saved lists. After, you save a definition by name and list audiences with their current counts.

{
  "name": "Manufacturers, 50 or more staff",
  "audience": {
    "schema": "mydatahub.audience/1",
    "where": {
      "all": [
        { "field": "company.sic_2007", "op": "in", "values": ["C"] },
        { "field": "company.employee_band", "op": "gte", "value": "50-99" }
      ]
    }
  }
}

That is the input of save_audience. Audiences are shared across your organisation, so the old forUserGroup and days arguments are not needed.

4. Ordering

Before, the model's own flag spent the credits.

{ "savedSelectionId": 1234, "packId": 548, "volumeRequired": 500, "confirmed": true }

That is create_data_extract. The old packId 548 is the Standard bundle. After, a quote comes first and a person confirms.

  1. quote_order with { "count_id": "cnt_7K2PQ4MAXN3R6TBY5WDZ", "field_set": "standard", "limit": 500 } returns a machine quote.
  2. create_order with the quote and an idempotency key. The host asks the person to approve, or an agent key within its limits places the order.
{
  "name": "create_order",
  "arguments": {
    "quote_id": "qte_01JB4M2R6T1Y5W8ZAK8Q2V7XN3",
    "idempotency_key": "6f1c3b2a-9d4e-4f8a-b1c2-7e5d9a0f3c21",
    "name": "Leeds marketing Q4"
  }
}

Every old packId and its field set is in Old bundles to field sets. See Spending safely.

5. Getting the records

Before, the legacy download endpoint paged records by number. It took OrderId, pageNumber and pageSize, and the key travelled in the X-Api-Key header.

POST https://app.mydatahub.uk/api/RunExtract
X-Api-Key: <your key>

After, GET /v1/orders/{id}/records pages by cursor with a bearer token. The default page is 50 records and the maximum 500, and a page can hold fewer when it reaches about 8,000 tokens.

curl "https://mydatahub.emailmovers.com/v1/orders/ord_01JB4N9S3W2X6Y7Z8A1B2C3D4E/records?limit=500" \
  -H "Authorization: Bearer $MYDATAHUB_API_KEY"

Follow next_cursor until has_more is false. Over MCP the tool is fetch_order_records, with at most 100 rows a page.

The plan is for legacy download responses to carry Deprecation and Sunset headers until the endpoint returns 410. The v2 API does not serve that endpoint yet.

6. Checking the balance

Before, get_credits returned the balance. After, get_account takes no arguments and returns the balance by source, the plan, per-action prices, the rate limit and any agent key budget.

{ "name": "get_account", "arguments": {} }

Migration checklist

  1. Make an mdh_test_ key in the app, and point your integration at it. Test keys return synthetic data and never charge.
  2. Replace X-Api-Key with Authorization: Bearer.
  3. Replace the session calls with one count_audience call per audience.
  4. Replace create_data_extract and the legacy download endpoint with quote_order, create_order and fetch_order_records.
  5. Decide how spends are confirmed: a person in the app, or an agent key with a budget and a per-order cap. See Authentication.
  6. Subscribe to order.ready instead of polling. See Webhooks.
  7. Switch to an mdh_live_ key when the test run passes.

Questions

Do I need to reinstall the connector? No. https://mcp.mydatahub.uk keeps serving today's product after launch, so an install made there keeps working as it is.

Once the new server supports OAuth sign-in from the client, that address passes MCP through to https://mydatahub.emailmovers.com/mcp with no redirect. To use the 22 new tools before then, add the new server with a MyDataHub API key, as the Quickstart for agents shows.

Will my saved lists and credits survive? That is the plan. Saved lists become audiences in the same organisation, and the credit balance carries over one for one with licence dates unchanged (to confirm).

Can an old agent still spend? Only through a person's confirmation or an agent key. A flag set by a model is never consent, in the old tool as in the new ones.

What happens when the window closes? The plan is that the old tools stop being listed, and that the old header and endpoint return 410. See Changelog and deprecation policy.

Next steps