> ## Documentation Index
> Fetch the complete documentation index at: https://docs.adrop.io/llms.txt
> Use this file to discover all available pages before exploring further.

# Option Groups

> Group targeting options into option groups and manage their sub-options with the Context Targeting API.

An option group is a targeting option that groups other options, called sub-options. With the API, you can create option groups, add and move sub-options, and choose whether advertisers can select sub-options individually.

The requests on this page need an API key with the `context-targeting:write` scope. Reading option groups needs `context-targeting:read`. See [Prerequisites](/integrations/context-targeting/overview#prerequisites).

The examples build a **Sports** option group with the sub-options **Baseball** and **Soccer** in the **News Sections** targeting (`01JAB3XK5Q8V7M2N4P6R9T0W1Y`). They assume the targeting has no other option named **Sports**, because option names must be unique within a targeting.

***

## What Is an Option Group

An option group matches its own Context IDs and the Context IDs of its sub-options. With the structure below, a campaign that includes **Sports** matches ad requests whose Context ID is `sport`, `baseball`, or `soccer`.

| Option | Type | Own Context IDs | Matches |
| - | - | - | - |
| **Sports** | Option group | `sport` | `sport`, `baseball`, `soccer` |
| **Baseball** | Sub-option of Sports | `baseball` | `baseball` |
| **Soccer** | Sub-option of Sports | `soccer` | `soccer` |

* **One level only**: Sub-options are always regular options (`type: "atomic"`). An option group is always a top-level option and can't contain another option group.
* **Never empty**: An option group needs at least one sub-option or one Context ID of its own.
* **Unique own Context IDs**: An option group's own Context IDs can't also be own Context IDs of another option group in the project. See [Constraints](#constraints).
* **Names**: Option group and sub-option names follow the [name rules](/integrations/context-targeting/errors#name-rules) and must be unique within the targeting, including option groups and sub-options. A duplicate fails with `409 TARGETING_CATEGORY_NAME_DUPLICATE`.

In the [option object](/integrations/context-targeting/options#option-object), option groups and sub-options have these fields:

| Field | Type | Description |
| - | - | - |
| `type` | string | `collection` for an option group. Sub-options are `atomic`. |
| `contextCount` | number | For an option group, the number of distinct Context IDs it matches: its own and its sub-options' |
| `ownContextCount` | number | Option group only. Number of its own Context IDs |
| `exposeChildren` | boolean | Option group only. Whether sub-options are shown to advertisers. `false` if never set |
| `children` | string\[] | Option group only. IDs of the first 50 sub-options |
| `childCount` | number | Option group only. Total number of sub-options |
| `parent` | string | Sub-option only. ID of the option group |

To read an option group:

* [Get an Option](/integrations/context-targeting/options#get-an-option) returns only the option group's own Context IDs in `contexts`, paged with `offset` and `limit` (`ownContextCount` in total). Read each sub-option's Context IDs from the sub-option.
* When `childCount` is more than 50, list all sub-options with [List Options](/integrations/context-targeting/options#list-options) and the `parent` query parameter set to the option group ID.

To rename an option group or a sub-option, or to change its Context IDs, use [Update an Option](/integrations/context-targeting/options#update-an-option) as you would for any option. For an option group, `contexts` changes only its own Context IDs. Changes reach ad serving after a short delay. See [When Changes Take Effect](/integrations/context-targeting/overview#when-changes-take-effect).

***

## Create an Option Group

Use the same endpoint as [Add an Option](/integrations/context-targeting/options#add-an-option) and send `type: "collection"`, `children`, or both. Each item in `children` becomes a new sub-option, created together with the option group.

### Request

```http theme={null}
POST /context-targeting/{targetingId}/category
```

### Path Parameters

| Parameter | Type | Required | Description |
| - | - | :-: | - |
| `targetingId` | string | Yes | ID of the targeting to add the option group to |

### Request Body

| Field | Type | Required | Description |
| - | - | :-: | - |
| `name` | string | Yes | Option group name |
| `type` | string | No | `collection`. You can leave it out when you send `children` |
| `contexts` | string\[] | No | The option group's own Context IDs |
| `children` | object\[] | No | Sub-options to create with the option group |
| `children[].name` | string | Yes | Sub-option name |
| `children[].contexts` | string\[] | No | Context IDs of the sub-option |
| `exposeChildren` | boolean | No | Show sub-options to advertisers. Default `false`. See [Show Sub-options to Advertisers](#show-sub-options-to-advertisers) |

* Send at least one item in `children` or one Context ID in `contexts`. An option group with neither fails with `400 TARGETING_CATEGORY_NOT_SUPPORTED`. So does `type: "atomic"` with `children`.
* An item in `children` accepts only `name` and `contexts`. A sub-option can't have sub-options of its own.
* If one of the option group's own Context IDs is already an own Context ID of another option group in the project, the request fails with `400 TARGETING_COLLECTION_CONTEXT_DUPLICATED`. See [Constraints](#constraints).
* A new option group is a new top-level option, so [Effects on Ad Products and Campaigns](/integrations/context-targeting/options#effects-on-ad-products-and-campaigns) applies to it as to any new option.

You can also create option groups when you [create a targeting](/integrations/context-targeting/targetings#create-a-targeting), with the same fields in each `categories` item, or with [Add Options in Bulk](/integrations/context-targeting/options#add-options-in-bulk).

### Example Request

```bash theme={null}
curl -X POST https://lake.adrop.io/context-targeting/01JAB3XK5Q8V7M2N4P6R9T0W1Y/category \
  -H "x-api-key: YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "name": "Sports",
    "type": "collection",
    "contexts": ["sport"],
    "children": [
      { "name": "Baseball", "contexts": ["baseball"] }
    ]
  }'
```

To create an option group with only its own Context IDs, keep `type: "collection"` and leave out `children`:

```json theme={null}
{
  "name": "Sports",
  "type": "collection",
  "contexts": ["sport"]
}
```

### Example Response

The API returns `201` with the new option group.

```json theme={null}
{
  "_id": "01JAB3XK5SB1C2D3E4F5G6H7J8",
  "name": "Sports",
  "type": "collection",
  "contextCount": 2,
  "ownContextCount": 1,
  "exposeChildren": false,
  "children": ["01JAB3XK5TC1D2E3F4G5H6J7K8"],
  "childCount": 1,
  "createdAt": "2026-10-13T01:23:45.000Z",
  "updatedAt": "2026-10-13T01:23:45.000Z"
}
```

***

## Add an Option to an Option Group

To add a new sub-option to an existing option group, send `parent` with the option group ID to the [Add an Option](/integrations/context-targeting/options#add-an-option) endpoint.

* `parent` must be a top-level option group in the same targeting. If the targeting has no option with that ID, the request fails with `404 TARGETING_CATEGORY_NOT_FOUND`. If the option isn't an option group, it fails with `400 TARGETING_CATEGORY_NOT_SUPPORTED`.
* The new option is a regular option. `type: "collection"`, `children`, or `exposeChildren` together with `parent` fails with `400 TARGETING_CATEGORY_NOT_SUPPORTED`.
* The option group starts matching the new option's Context IDs. Campaigns that include the option group now also target them, and campaigns that exclude it now also exclude them. Ad product settings don't change.

To put an existing option into an option group, see [Move or Detach an Option](#move-or-detach-an-option).

### Request

```http theme={null}
POST /context-targeting/{targetingId}/category
```

### Path Parameters

| Parameter | Type | Required | Description |
| - | - | :-: | - |
| `targetingId` | string | Yes | ID of the targeting that has the option group |

### Request Body

| Field | Type | Required | Description |
| - | - | :-: | - |
| `name` | string | Yes | Sub-option name |
| `contexts` | string\[] | No | Context IDs of the sub-option |
| `parent` | string | Yes | ID of the option group to add the option to |

### Example Request

```bash theme={null}
curl -X POST https://lake.adrop.io/context-targeting/01JAB3XK5Q8V7M2N4P6R9T0W1Y/category \
  -H "x-api-key: YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "name": "Soccer",
    "contexts": ["soccer"],
    "parent": "01JAB3XK5SB1C2D3E4F5G6H7J8"
  }'
```

### Example Response

The API returns `201` with the new sub-option.

```json theme={null}
{
  "_id": "01JAB3XK5TC1D2E3F4G5H6J7K9",
  "name": "Soccer",
  "type": "atomic",
  "contextCount": 1,
  "parent": "01JAB3XK5SB1C2D3E4F5G6H7J8",
  "createdAt": "2026-10-13T01:25:10.000Z",
  "updatedAt": "2026-10-13T01:25:10.000Z"
}
```

***

## Move or Detach an Option

Send `parent` to [Update an Option](/integrations/context-targeting/options#update-an-option) to change where an option sits.

* An option group ID moves the option under that option group, from the top level or from another option group.
* `null` detaches a sub-option to the top level.

| Move | Effect on ad products and campaigns |
| - | - |
| Top level → option group | The option's own settings in ad products are removed. The move fails with `400 TARGETING_CATEGORY_LOCKED` if an ad product sets the option as **Required Include** or **Required Exclude**, or if a campaign that hasn't ended includes or excludes it (deleted or canceled campaigns and drafts created by advertisers don't count). Remove those settings first. |
| Option group → top level | The option takes over the option group's settings in ad products and campaigns, so campaigns that included or excluded the option group still include or exclude the option's Context IDs. If this would give an ad product more than 500 options set as **Required Include** or **Required Exclude**, the request fails with `400 AD_PRODUCT_TARGETING_POLICY_LIMIT_EXCEEDED`. |
| Option group → another option group | Ad products and campaigns don't change. |

The option group that receives the option starts matching its Context IDs, and the option group that the option leaves stops matching them.

* You can't move an option group. `parent` on an option group fails with `400 TARGETING_CATEGORY_NOT_SUPPORTED`.
* The destination must be a top-level option group in the same targeting. Otherwise the request fails with `404 TARGETING_CATEGORY_NOT_FOUND` or `400 TARGETING_CATEGORY_NOT_SUPPORTED`.
* You can't move or detach the last sub-option of an option group that has no Context IDs of its own. The request fails with `400 TARGETING_CATEGORY_NOT_SUPPORTED`.

<Warning>
  The lock applies even when the option got its **Required Include**, **Required Exclude**, or campaign include or exclude automatically, for example from its option group when it was detached or when the option group was deleted. Until that setting is removed, you can't move the option back under an option group with the API. To keep options in an option group, create them with `parent` or `children` from the start.
</Warning>

### Request

```http theme={null}
PATCH /context-targeting/category/{categoryId}
```

### Path Parameters

| Parameter | Type | Required | Description |
| - | - | :-: | - |
| `categoryId` | string | Yes | ID of the option to move |

### Request Body

| Field | Type | Required | Description |
| - | - | :-: | - |
| `parent` | string or null | Yes | Option group ID to move the option under, or `null` to detach it to the top level |
| `name` | string | No | New name, applied together with the move. If the move fails, the name doesn't change either |

`parent` can't be combined with `contexts`. Change Context IDs in a separate request.

### Example Request

Detach **Soccer** to the top level:

```bash theme={null}
curl -X PATCH https://lake.adrop.io/context-targeting/category/01JAB3XK5TC1D2E3F4G5H6J7K9 \
  -H "x-api-key: YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{ "parent": null }'
```

To move it back under **Sports**, send the option group ID instead:

```json theme={null}
{
  "parent": "01JAB3XK5SB1C2D3E4F5G6H7J8"
}
```

### Example Response

The API returns `200` with the updated option. A detached option has no `parent`.

```json theme={null}
{
  "_id": "01JAB3XK5TC1D2E3F4G5H6J7K9",
  "name": "Soccer",
  "type": "atomic",
  "contextCount": 1,
  "createdAt": "2026-10-13T01:25:10.000Z",
  "updatedAt": "2026-10-13T01:30:00.000Z"
}
```

***

## Show Sub-Options to Advertisers

By default, advertisers can select an option group only as a whole. Set `exposeChildren` to `true` to let advertisers include or exclude each sub-option on its own when they book a campaign.

* Advertisers can select sub-options only when the ad product offers the option group as **Free Selection**. See [Selling Targeting](/targeting/sell).
* `exposeChildren` doesn't change what the option group matches. Changing it doesn't modify existing ad products or campaigns.
* Only option groups accept `exposeChildren`. Sending it for any other option fails with `400 TARGETING_CATEGORY_NOT_SUPPORTED`. Send `true` or `false`; `null` fails with `400`.
* You can also set it when you [create the option group](#create-an-option-group).

### Request

```http theme={null}
PATCH /context-targeting/category/{categoryId}
```

### Path Parameters

| Parameter | Type | Required | Description |
| - | - | :-: | - |
| `categoryId` | string | Yes | Option group ID |

### Request Body

| Field | Type | Required | Description |
| - | - | :-: | - |
| `exposeChildren` | boolean | Yes | `true` shows sub-options to advertisers, `false` hides them |

You can send `name` and `contexts` in the same request.

### Example Request

```bash theme={null}
curl -X PATCH https://lake.adrop.io/context-targeting/category/01JAB3XK5SB1C2D3E4F5G6H7J8 \
  -H "x-api-key: YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{ "exposeChildren": true }'
```

### Example Response

The API returns `200` with the updated option group.

```json theme={null}
{
  "_id": "01JAB3XK5SB1C2D3E4F5G6H7J8",
  "name": "Sports",
  "type": "collection",
  "contextCount": 3,
  "ownContextCount": 1,
  "exposeChildren": true,
  "children": ["01JAB3XK5TC1D2E3F4G5H6J7K8", "01JAB3XK5TC1D2E3F4G5H6J7K9"],
  "childCount": 2,
  "createdAt": "2026-10-13T01:23:45.000Z",
  "updatedAt": "2026-10-13T01:35:00.000Z"
}
```

***

## Delete an Option Group

Delete an option group with [Delete an Option](/integrations/context-targeting/options#delete-an-option). Its sub-options aren't deleted:

* Sub-options move to the top level and take over the option group's settings in ad products and campaigns. For example, if an ad product sets **Sports** as **Required Include**, **Baseball** and **Soccer** become **Required Include**, and a campaign that included Sports now includes Baseball and Soccer.
* The option group's own Context IDs are deleted and stop matching. In the example, campaigns that included Sports no longer match `sport`.
* If taking over the settings would give an ad product more than 500 options set as **Required Include** or **Required Exclude**, the request fails with `400 AD_PRODUCT_TARGETING_POLICY_LIMIT_EXCEEDED` and nothing is deleted.

Campaigns don't block the deletion. If the option group has no sub-options, campaigns that included it lose that condition. See [Effects on Ad Products and Campaigns](/integrations/context-targeting/options#effects-on-ad-products-and-campaigns).

<Note>
  In the console, **Delete** on a group also deletes its sub-options. Deleting an option group with the API works like **Ungroup** in the console: its sub-options stay as top-level options.
</Note>

### Request

```http theme={null}
DELETE /context-targeting/category/{categoryId}
```

### Path Parameters

| Parameter | Type | Required | Description |
| - | - | :-: | - |
| `categoryId` | string | Yes | Option group ID |

### Example Request

```bash theme={null}
curl -X DELETE https://lake.adrop.io/context-targeting/category/01JAB3XK5SB1C2D3E4F5G6H7J8 \
  -H "x-api-key: YOUR_API_KEY" \
  -H "Content-Type: application/json"
```

### Example Response

The API returns `200` with the ID of the deleted option group.

```json theme={null}
{
  "_id": "01JAB3XK5SB1C2D3E4F5G6H7J8"
}
```

***

## Constraints

**Structure**

* An option group can't be moved, and an option's type can't change after it's created. To group existing options, create an option group and [move](#move-or-detach-an-option) the options under it. A new option group can't be empty, so create it with a Context ID of its own or a new sub-option.
* Sub-options created in `children` count toward the per-request and per-targeting option limits. See [Limits](/integrations/context-targeting/errors#limits).

**Own Context IDs are unique among option groups**

* An option group's own Context IDs can't be own Context IDs of another option group in the project, in any context targeting. Regular options and sub-options can share Context IDs with any option.
* The rule applies when you create an option group and when you add own Context IDs to an option group with `add` or `set`. A conflict fails with `400 TARGETING_COLLECTION_CONTEXT_DUPLICATED`, and `detail.conflicts` lists up to 20 of the conflicting Context IDs with the option groups that already have them. See [Error Codes](/integrations/context-targeting/errors#error-codes).

**An option group can't become empty**

| Request | Result |
| - | - |
| Delete, move, or detach the last sub-option of an option group that has no Context IDs of its own | `400 TARGETING_CATEGORY_NOT_SUPPORTED`. Delete the option group instead; the sub-option moves to the top level. |
| Remove the last own Context ID of an option group that has no sub-options, with `remove` or `set: []` | `400 TARGETING_CATEGORY_NOT_SUPPORTED` |

An option group with its own Context IDs can lose all of its sub-options. It stays as an option group with only its own Context IDs.

**Option group items per bulk request**

In one option bulk request, at most 150 items can be option group items. A request with more fails as a whole with `400`, and nothing is applied. Split the items and send the requests one after another.

| Bulk request | Option group items |
| - | - |
| [Add Options in Bulk](/integrations/context-targeting/options#add-options-in-bulk) | Items that create an option group (`type: "collection"` or `children`), and items with `parent` |
| [Update Options in Bulk](/integrations/context-targeting/options#update-options-in-bulk) | Items with `parent`, including `null` |
| [Delete Options in Bulk](/integrations/context-targeting/options#delete-options-in-bulk) | IDs of option groups and sub-options |

* For a bulk delete, the error code is `TARGETING_CATEGORY_TREE_ITEMS_LIMIT_EXCEEDED`. `detail.limit` is the limit, and `detail.count` is the number of option groups and sub-options in your request.
* Renaming an option group, changing its own Context IDs, and setting `exposeChildren` don't count as option group items.

**Don't delete an option group and its sub-options in the same bulk request**

The result depends on their order in `ids`:

* **Option group first**: The sub-options move to the top level, take over the option group's settings, and are then deleted as top-level options. Campaigns that included the option group can lose that condition.
* **Sub-options first**: If the option group has no Context IDs of its own, deleting the last sub-option fails with `400 TARGETING_CATEGORY_NOT_SUPPORTED`. That option then moves to the top level when the option group is deleted.


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.