# Expand job titles

> POST /v1/job-titles/expand. 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.

Source: https://emailmovers.com/mydatahub/docs/reference/rest/expand-job-titles

`POST /v1/job-titles/expand`

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.

| Property | Value |
| --- | --- |
| Endpoint | `POST https://mydatahub.emailmovers.com/v1/job-titles/expand` |
| Operation id | `expandJobTitles` |
| Cost | Free (0 credits) |
| Access | No account, key or token needed. |
| MCP tool | [`expand_job_titles`](https://emailmovers.com/mydatahub/docs/reference/mcp/expand_job_titles.md) |
| Tag | Catalogue |

> **Not yet live.** MyDataHub v2 is built and tested locally and is not yet live. The v2 hosts open at launch, date to confirm. This page describes the contract the API is tested against.

## Request

### Parameters

| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `MyDataHub-Version` | header | string | no | The dated API version, pinned per key when left out. Non-breaking changes ship as new dates; the path major changes only for breaking changes, with a 12 month deprecation window. |

### Body

| Member | Type | Required | Description |
| --- | --- | --- | --- |
| `terms` | array of string | no | Title keywords to expand, such as marketing manager. |
| `bundle` | enum: `sdm`, `manager_and_above`, `owner_managed` | no | A title bundle to show: sdm, manager_and_above or owner_managed. |
| `mode` | enum: `contains`, `starts_with`, `exact` | no | contains (the default) matches titles containing a term, starts_with matches titles beginning with it, exact takes verbatim titles with no expansion. |
| `expand` | array of enum: `plural_singular`, `department_synonyms`, `ampersand_and`, `abbreviations`, `permutations` | no | Expansion sets to apply, each once. Left out, the four default sets apply. |
| `dataset` | enum: `uk_contacts`, `ie_contacts`, `global_contacts` | no | The dataset an audience draws from. uk_contacts is live; ie_contacts and global_contacts are listed but not yet served. |

## Example request

```bash
curl -X POST "https://mydatahub.emailmovers.com/v1/job-titles/expand" \
  -H "Content-Type: application/json" \
  --data '{
  "terms": ["marketing manager"],
  "mode": "contains"
}'
```

## Responses

### 200: The expanded titles.

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

| Member | Type | Always present | Description |
| --- | --- | --- | --- |
| `dataset_version` | string | yes | The immutable weekly build a count is bound to, such as 2026-W39. The last 52 stay queryable. |
| `mode` | enum: `contains`, `starts_with`, `exact` | yes | contains (the default) matches titles containing a term, starts_with matches titles beginning with it, exact takes verbatim titles with no expansion. |
| `expand` | array of enum: `plural_singular`, `department_synonyms`, `ampersand_and`, `abbreviations`, `permutations` | yes |  |
| `terms` | array of `ExpandedTerm` | yes |  |
| `terms[].term` | string | yes | A matched title. |
| `terms[].from_term` | string | yes | The input term it came from. |
| `terms[].expansion_set` | enum: `plural_singular`, `department_synonyms`, `ampersand_and`, `abbreviations`, `permutations` or `input` | yes | The expansion set that produced it, or input for the term itself. |
| `terms[].contacts` | integer (0 or more) or `under 10` or `suppressed` | yes | 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. |
| `terms[].excluded` | boolean | no | True when exclude_terms removed it from the audience. |
| `total_contacts` | integer (0 or more) or `under 10` or `suppressed` | yes | 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. |
| `bundle` | object or null | yes |  |
| `bundle.id` | enum: `sdm`, `manager_and_above`, `owner_managed` | yes |  |
| `bundle.label` | string | yes |  |
| `bundle.status` | enum: `confirmed`, `proposed`, `to_confirm` | yes |  |
| `bundle.resolves_to` | `WhereNode` | yes | 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. |
| `filter` | `Filter` or null | yes | A ready matches filter for the terms, or null for a bundle. |
| `disclosure` | `Disclosure` | yes | Whether these numbers are exact or protected: "under 10" cells, rounding to 5 and secondary suppression for anonymous and trial callers. |
| `disclosure.level` | enum: `exact`, `rounded` | yes | exact for authenticated paid callers; rounded for anonymous and trial callers, whose counts under 100 and every cell are rounded to the nearest 5. |
| `disclosure.rounded_to` | integer or null | yes | 5 when numbers are rounded; null when they are exact. |
| `disclosure.secondary_suppression` | boolean | yes | True when cross-tab or breakdown cells of 10 or more were hidden as "suppressed" so totals cannot be differenced. |
| `disclosure.notes` | array of string | yes |  |

#### Example response

```json
{
  "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."
    ]
  }
}
```

## Errors

Every error is a problem object with a `code` and a `next_step`. Every response carries the `RateLimit` and `RateLimit-Policy` headers; see [Rate limits](https://emailmovers.com/mydatahub/docs/rate-limits.md).

| Status | Problem codes | When |
| --- | --- | --- |
| 400 | [`invalid-request`](https://emailmovers.com/mydatahub/docs/errors/invalid-request.md), [`idempotency-key-required`](https://emailmovers.com/mydatahub/docs/errors/idempotency-key-required.md), [`unsupported-version`](https://emailmovers.com/mydatahub/docs/errors/unsupported-version.md) | The request could not be read, or an Idempotency-Key is missing. |
| 422 | [`validation-failed`](https://emailmovers.com/mydatahub/docs/errors/validation-failed.md), [`quote-required`](https://emailmovers.com/mydatahub/docs/errors/quote-required.md), [`dataset-unavailable`](https://emailmovers.com/mydatahub/docs/errors/dataset-unavailable.md) | The request is not valid. errors lists every problem with a JSON pointer and did_you_mean. |
| 429 | [`rate-limited`](https://emailmovers.com/mydatahub/docs/errors/rate-limited.md) | Too many requests. Wait for Retry-After. |
| Any other failure | [`internal-error`](https://emailmovers.com/mydatahub/docs/errors/internal-error.md), [`service-unavailable`](https://emailmovers.com/mydatahub/docs/errors/service-unavailable.md), [`tps-screen-expired`](https://emailmovers.com/mydatahub/docs/errors/tps-screen-expired.md) | Something failed on our side (500 internal-error) or the service is briefly unavailable (503 service-unavailable, with Retry-After). Nothing was charged; retry with the same Idempotency-Key. A count, sample, quote or order with telephone in play is also refused with 503 tps-screen-expired, with no Retry-After, while the dataset version's TPS and CTPS screen is more than 28 days old. |
