> ## 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.

# Context Targeting API

> Manage context targetings, targeting options, and Context IDs from your server with an API key.

The Context Targeting API lets you manage context targeting from your server. Map the sections, boards, or tags in your CMS to targeting options and Context IDs, and update them automatically as your content changes instead of editing them by hand in the console.

The API works on the same data as context targeting in the console. Targetings you create with the API appear in the console, and the API can read targetings created in the console.

***

## Prerequisites

* An API key with both the `context-targeting:read` and `context-targeting:write` scopes. A project owner creates the key in **\[Settings]** > **\[Integrations]** and selects both scopes. See [Integrations & API Keys](/integrations/overview) for how to create a key.
* You can't change the scopes of an existing key. If your current key doesn't have both scopes, create a new key, and revoke the old one if you no longer need it.

Grant both scopes, even if you mostly write. Each endpoint checks a single scope, and `context-targeting:write` doesn't include `context-targeting:read`. A key with only the write scope gets `403` on every `GET` request, so it can't look up IDs, read the current Context IDs, or check whether a change has been loaded.

<Warning>
  Call the API only from your server. Don't include the API key in apps or web pages.
</Warning>

***

## Basic Information

| Item | Value |
| - | - |
| Base URL | `https://lake.adrop.io` |
| Authentication | API key in the `x-api-key` header. The key determines the project, so paths don't include a project ID. |
| Content-Type | `application/json` |
| Max request body | 8 MB. A larger request returns `413`. |
| ID format | 26-character ULID issued by the server, such as `01JAB3XK5Q8V7M2N4P6R9T0W1Y`. An ID that isn't a valid ULID returns `400`. |
| Write processing | The API processes writes for a project one at a time. A write sent while another write is in progress can fail with `409 CONTEXT_TARGETING_WRITE_IN_PROGRESS`, so send writes one after another, not in parallel. Reads aren't affected. See [Concurrent Writes](/integrations/context-targeting/errors#concurrent-writes). |

For all request limits, see [Limits](/integrations/context-targeting/errors#limits).

***

## Terms

Some API paths and fields use different names from the console. These docs use the console's terms.

| API | Console term | Description |
| - | - | - |
| `targeting` | Targeting | A context targeting, such as "News Sections". The API handles context targetings only. |
| `category` | Targeting option (option) | An option in a targeting, such as "Sports". |
| `contexts` | Context ID | The IDs registered to an option, such as `sport`. Your app or website sends them as `contextId` in ad requests. |
| `type: "collection"` | Option group | An option that groups sub-options. It matches its own Context IDs and those of its sub-options. A regular option has `type: "atomic"`. |
| `children`, `parent` | Sub-option | An option inside an option group. An option group's `children` lists its sub-options, and a sub-option's `parent` is its option group. |
| `exposeChildren` | Show sub-options to advertisers | Whether advertisers can select each sub-option of an option group individually. It doesn't change ad serving. |

***

## Endpoints

Paths are relative to the base URL. Each endpoint requires the scope shown.

| Method | Path | Scope | Description |
| - | - | - | - |
| `GET` | `/context-targeting` | `context-targeting:read` | [List targetings](/integrations/context-targeting/targetings#list-targetings) |
| `GET` | `/context-targeting/{targetingId}` | `context-targeting:read` | [Get a targeting](/integrations/context-targeting/targetings#get-a-targeting) and its options |
| `POST` | `/context-targeting` | `context-targeting:write` | [Create a targeting](/integrations/context-targeting/targetings#create-a-targeting), optionally with options |
| `PATCH` | `/context-targeting/{targetingId}` | `context-targeting:write` | [Rename a targeting](/integrations/context-targeting/targetings#rename-a-targeting) |
| `DELETE` | `/context-targeting/{targetingId}` | `context-targeting:write` | [Delete a targeting](/integrations/context-targeting/targetings#delete-a-targeting) and all of its options |
| `POST` | `/context-targeting/bulk/create` | `context-targeting:write` | [Create targetings in bulk](/integrations/context-targeting/targetings#create-targetings-in-bulk), up to 100 per request |
| `POST` | `/context-targeting/bulk/update` | `context-targeting:write` | [Rename targetings in bulk](/integrations/context-targeting/targetings#rename-targetings-in-bulk), up to 100 per request |
| `POST` | `/context-targeting/bulk/delete` | `context-targeting:write` | [Delete targetings in bulk](/integrations/context-targeting/targetings#delete-targetings-in-bulk), up to 100 per request |
| `GET` | `/context-targeting/{targetingId}/categories` | `context-targeting:read` | [List the options](/integrations/context-targeting/options#list-options) of a targeting, page by page |
| `GET` | `/context-targeting/category/{categoryId}` | `context-targeting:read` | [Get an option](/integrations/context-targeting/options#get-an-option) with its Context IDs |
| `POST` | `/context-targeting/{targetingId}/category` | `context-targeting:write` | [Add an option](/integrations/context-targeting/options#add-an-option) to a targeting |
| `PATCH` | `/context-targeting/category/{categoryId}` | `context-targeting:write` | [Update an option](/integrations/context-targeting/options#update-an-option): rename it, change its Context IDs, move it, or set `exposeChildren` |
| `DELETE` | `/context-targeting/category/{categoryId}` | `context-targeting:write` | [Delete an option](/integrations/context-targeting/options#delete-an-option) |
| `POST` | `/context-targeting/category/bulk/create` | `context-targeting:write` | [Add options in bulk](/integrations/context-targeting/options#add-options-in-bulk), up to 5,000 per request |
| `POST` | `/context-targeting/category/bulk/update` | `context-targeting:write` | [Update options in bulk](/integrations/context-targeting/options#update-options-in-bulk), up to 5,000 per request |
| `POST` | `/context-targeting/category/bulk/delete` | `context-targeting:write` | [Delete options in bulk](/integrations/context-targeting/options#delete-options-in-bulk), up to 5,000 per request |

Bulk endpoints return a result for each item. See [Bulk Responses](/integrations/context-targeting/errors#bulk-responses).

***

## Quick Start

This example creates a "News Sections" targeting, changes the Context IDs of one option, and checks when the change has been loaded.

<Steps>
  <Step title="Create a targeting with options">
    Create the targeting and its options in one request.

    ```bash theme={null}
    curl -X POST https://lake.adrop.io/context-targeting \
      -H "x-api-key: YOUR_API_KEY" \
      -H "Content-Type: application/json" \
      -d '{
        "name": "News Sections",
        "categories": [
          { "name": "Sports", "contexts": ["sport", "baseball", "soccer"] },
          { "name": "Tech", "contexts": ["tech", "ai", "mobile"] }
        ]
      }'
    ```

    The response is `201` with the new targeting. Save the `_id` of the targeting and of each option. You need them to change the targeting later.

    ```json theme={null}
    {
      "_id": "01JAB3XK5Q8V7M2N4P6R9T0W1Y",
      "name": "News Sections",
      "categories": [
        {
          "_id": "01JAB3XK5RA1B2C3D4E5F6G7H8",
          "name": "Sports",
          "type": "atomic",
          "contextCount": 3,
          "createdAt": "2026-10-13T01:23:45.000Z",
          "updatedAt": "2026-10-13T01:23:45.000Z"
        },
        {
          "_id": "01JAB3XK5RA1B2C3D4E5F6G7H9",
          "name": "Tech",
          "type": "atomic",
          "contextCount": 3,
          "createdAt": "2026-10-13T01:23:45.000Z",
          "updatedAt": "2026-10-13T01:23:45.000Z"
        }
      ],
      "categoryCount": 2,
      "createdAt": "2026-10-13T01:23:45.000Z",
      "updatedAt": "2026-10-13T01:23:45.000Z"
    }
    ```

    <Note>
      Creating isn't idempotent. If a create request times out, see [Retries and Duplicate Requests](/integrations/context-targeting/errors#retries-and-duplicate-requests) before you send it again.
    </Note>
  </Step>

  <Step title="Add and remove Context IDs">
    When your sections change, update the Context IDs of the option. `add` and `remove` change only the IDs you list.

    ```bash theme={null}
    curl -X PATCH https://lake.adrop.io/context-targeting/category/01JAB3XK5RA1B2C3D4E5F6G7H8 \
      -H "x-api-key: YOUR_API_KEY" \
      -H "Content-Type: application/json" \
      -d '{
        "contexts": {
          "add": ["basketball", "golf"],
          "remove": ["soccer"]
        }
      }'
    ```

    The response is `200` with the updated option.

    ```json theme={null}
    {
      "_id": "01JAB3XK5RA1B2C3D4E5F6G7H8",
      "name": "Sports",
      "type": "atomic",
      "contextCount": 4,
      "createdAt": "2026-10-13T01:23:45.000Z",
      "updatedAt": "2026-10-13T01:30:00.000Z"
    }
    ```

    To replace all Context IDs of an option at once, see [Updating Context IDs](/integrations/context-targeting/options#updating-context-ids).
  </Step>

  <Step title="Check that the change has been loaded">
    Read the targeting and compare its `syncedAt` with the time your update succeeded.

    ```bash theme={null}
    curl "https://lake.adrop.io/context-targeting/01JAB3XK5Q8V7M2N4P6R9T0W1Y?includeCategories=false" \
      -H "x-api-key: YOUR_API_KEY" \
      -H "Content-Type: application/json"
    ```

    ```json theme={null}
    {
      "_id": "01JAB3XK5Q8V7M2N4P6R9T0W1Y",
      "name": "News Sections",
      "categoryCount": 2,
      "syncedAt": 1791855005000,
      "createdAt": "2026-10-13T01:23:45.000Z",
      "updatedAt": "2026-10-13T01:30:06.000Z"
    }
    ```

    The update in the previous step succeeded at 2026-10-13T01:30:00.000Z (`1791855000000`). `syncedAt` is later than that, so the change has been loaded, and ad serving picks it up within a few more minutes. If `syncedAt` is missing or earlier, read the targeting again after a short wait. See [When Changes Take Effect](#when-changes-take-effect).
  </Step>

  <Step title="Send Context IDs in ad requests">
    Send the Context ID of the page a user is viewing as `contextId` in your ad requests. After the change takes effect, an ad request with `contextId` set to `basketball` matches the "Sports" option.

    See [Creating Context Targeting](/targeting/context) for how Context IDs work in ad requests.
  </Step>
</Steps>

***

## When Changes Take Effect

A successful write is saved right away. Adrop then loads the project's context targetings into ad serving, and the change usually reaches ad serving within a few minutes.

Use the `syncedAt` field of a targeting (epoch milliseconds) to check whether a change has been loaded.

* `syncedAt` is the time the most recent load of the targeting started. A change that succeeded at or before `syncedAt` has been loaded, and ad serving picks it up within a few more minutes.
* `syncedAt` is omitted for a new targeting. It's also omitted after you change Context IDs (`add`, `remove`, or `set`), add an option, or move or detach an option. It usually comes back when the next load finishes.
* A load that was already running when you made a change can set `syncedAt` to that load's start time, which is earlier than your change. That load may not include your change, but a later load does.
* Renaming or deleting doesn't clear `syncedAt`.

So don't decide based on whether `syncedAt` is present. Record the time your request succeeded, then read the targeting that contains the change, for example with [Get a Targeting](/integrations/context-targeting/targetings#get-a-targeting) and `includeCategories=false`. The change has been loaded when `syncedAt` is equal to or later than that time. `syncedAt` uses Adrop's server clock, so allow a margin if your clock may differ.

<Tip>
  Until a change takes effect, ad requests are matched against the previous Context IDs. Add a new Context ID before your app or website starts sending it in ad requests.
</Tip>

***

## Related Documents

<CardGroup cols={2}>
  <Card title="Targetings" icon="bullseye" href="/integrations/context-targeting/targetings">
    List, create, rename, and delete targetings
  </Card>

  <Card title="Targeting Options" icon="tags" href="/integrations/context-targeting/options">
    Add options and keep their Context IDs up to date
  </Card>

  <Card title="Option groups" icon="folder-tree" href="/integrations/context-targeting/option-groups">
    Group options into an option group with sub-options
  </Card>

  <Card title="Errors and Limits" icon="triangle-exclamation" href="/integrations/context-targeting/errors">
    Error codes, limits, and how to retry
  </Card>

  <Card title="Integrations & API Keys" icon="key" href="/integrations/overview">
    Create and manage API keys
  </Card>

  <Card title="Creating Context Targeting" icon="book" href="/targeting/context">
    How context targeting and Context IDs work
  </Card>

  <Card title="Selling Targeting" icon="store" href="/targeting/sell">
    Connect targetings to ad products and sell them to advertisers
  </Card>
</CardGroup>


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