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

# 오류와 한도

> 문맥 타겟팅 API가 오류를 알리는 방식과 요청을 안전하게 다시 보내는 방법, 요청에 적용되는 한도를 안내합니다.

이 페이지에서는 [문맥 타겟팅 API](/ko/integrations/context-targeting/overview)의 오류 응답, 일괄 요청의 결과를 알리는 방식, 안전하게 재시도하는 방법, 요청에 적용되는 한도를 설명합니다.

***

## 오류 응답

실패한 요청은 HTTP 오류 상태와 함께 아래 형식 중 하나의 JSON 본문을 반환합니다. 오류는 `message` 문구가 아니라 HTTP 상태와 `errorCode`로 구분해 처리하세요.

| 상태 | 형식 | `errorCode` |
| - | - | :-: |
| `400` | [검증 오류](#검증-오류) | 없음 |
| `400`, `404`, `409` | [비즈니스 오류](#비즈니스-오류) | 있음 |
| `401`, `403` | [인증과 권한 오류](#인증과-권한-오류) | 없음 |
| `413` | [요청 본문 크기 초과](#요청-본문-크기-초과) | 없음 |
| `500`, `502` | [서버 오류](#서버-오류) | 없음 |

### 검증 오류

요청이 처리되기 전에 거부된 경우입니다. 필드가 빠졌거나 잘못된 경우, 이름이나 컨텍스트 ID가 규칙에 맞지 않는 경우, 요청에 허용되지 않는 필드(예: 생성 요청의 `_id`)가 있는 경우, 요청당 한도를 넘은 경우가 여기에 해당합니다. 일괄 요청이라도 요청의 어떤 내용도 적용되지 않습니다.

`message`는 발견된 문제를 모두 나열한 배열입니다.

```json theme={null}
{
  "message": [
    "property _id should not exist",
    "name must be 2-36 characters of Korean, English letters, digits, spaces or ~%&+- (no leading or trailing spaces)"
  ],
  "error": "Bad Request",
  "statusCode": 400
}
```

경로의 ID가 올바른 ULID가 아니거나 본문이 올바른 JSON이 아니면 `message`는 문자열 하나입니다.

```json theme={null}
{
  "message": "Invalid ULID",
  "error": "Bad Request",
  "statusCode": 400
}
```

### 비즈니스 오류

요청 형식은 올바르지만 서버가 처리할 수 없는 경우입니다. 예를 들어 이름이 이미 사용 중이거나 타겟팅이 없는 경우입니다.

| 필드 | 타입 | 설명 |
| - | - | - |
| `statusCode` | number | HTTP 상태 코드 |
| `errorCode` | string | 오류 코드. [코드별 오류](#코드별-오류)를 참고하세요. |
| `detail` | object | 일부 오류 코드에서 제공하는 추가 정보. [detail 필드](#detail-필드)를 참고하세요. |
| `timestamp` | string | 오류가 발생한 시각. 일부 응답에만 포함됩니다. |

다른 문맥 타겟팅이 이미 쓰고 있는 이름으로 타겟팅을 생성하면 다음과 같이 응답합니다.

```json theme={null}
{
  "statusCode": 409,
  "errorCode": "TARGETING_NAME_DUPLICATE",
  "detail": {
    "existingId": "01JAB3XK5Q8V7M2N4P6R9T0W1Y"
  }
}
```

단건 엔드포인트는 항목의 오류를 그대로 응답으로 반환합니다. 단, 예기치 않은 오류로 항목이 실패하면 대신 `502`를 반환합니다. 일괄 작업 엔드포인트는 항목별 오류를 `results`에 담아 반환합니다. [일괄 작업 응답](#일괄-작업-응답)을 참고하세요.

### 인증과 권한 오류

`401`은 `x-api-key` 헤더가 없거나, 키가 유효하지 않거나 폐기된 경우입니다. `403`은 엔드포인트에 필요한 권한(scope)이 키에 없는 경우입니다.

```json theme={null}
{
  "message": "Unauthorized",
  "statusCode": 401
}
```

```json theme={null}
{
  "message": "Forbidden",
  "statusCode": 403
}
```

### 요청 본문 크기 초과

`413`은 요청 본문이 8 MB보다 큰 경우입니다. 아무것도 적용되지 않습니다.

```json theme={null}
{
  "statusCode": 413,
  "message": "request entity too large"
}
```

### 서버 오류

`502`는 일시적인 서버 문제로 요청을 완료하지 못한 경우이고, `500`은 예기치 않은 서버 오류입니다. 두 경우 모두 요청이 적용됐을 수도 있고 적용되지 않았을 수도 있습니다. `message` 문구는 달라질 수 있습니다. [재시도와 중복 요청](#재시도와-중복-요청)을 참고하세요.

```json theme={null}
{
  "message": "Upstream service error",
  "error": "Bad Gateway",
  "statusCode": 502
}
```

***

## 오류 코드

### 상태별 오류

다음 오류에는 `errorCode`가 없습니다.

| 상태 | 발생 조건 | 대처 방법 |
| - | - | - |
| `400` | 요청이 검증을 통과하지 못했습니다. 아무것도 적용되지 않습니다. | `message`를 보고 요청을 고치세요. [한도](#한도)와 [이름 규칙](#이름-규칙)을 참고하세요. |
| `401` | `x-api-key` 헤더가 없거나, 키가 유효하지 않거나 폐기되었습니다. | API 키를 확인하세요. |
| `403` | 키에 필요한 권한이 없습니다. `GET` 요청에는 `context-targeting:read`가, `POST`, `PATCH`, `DELETE` 요청에는 `context-targeting:write`가 필요합니다. | 그 권한이 있는 키를 사용하세요. 이미 발급한 키에는 권한을 추가할 수 없으므로 **\[관리]** > **\[연동]** 메뉴에서 새 키를 발급하세요. [연동 & API 키](/ko/integrations/overview)를 참고하세요. |
| `413` | 요청 본문이 8 MB보다 큽니다. | 요청을 나눠 보내세요. 한글 같은 비ASCII 문자는 `\uXXXX` 이스케이프 대신 UTF-8 그대로 보내세요(예: Python의 `json.dumps`에는 `ensure_ascii=False`, PHP의 `json_encode`에는 `JSON_UNESCAPED_UNICODE` 사용). |
| `500`, `502` | 서버 문제입니다. 요청이 적용됐을 수도 있고 적용되지 않았을 수도 있습니다. | 잠시 기다린 뒤 같은 요청을 다시 보내세요. [재시도와 중복 요청](#재시도와-중복-요청)을 참고하세요. |

### 코드별 오류

| 상태 | `errorCode` | 발생 조건 | 대처 방법 |
| - | - | - | - |
| `400` | `AD_PRODUCT_TARGETING_POLICY_LIMIT_EXCEEDED` | 변경 후 광고 상품에서 **필수 포함** 또는 **필수 제외**로 설정된 옵션이 모든 타겟팅을 합쳐 500개를 넘게 됩니다. 새 최상위 옵션이 광고 상품의 필수 설정을 이어받을 때나, 옵션 그룹을 삭제하거나 하위 옵션을 분리해 하위 옵션이 옵션 그룹의 설정을 이어받을 때 발생할 수 있습니다. 옵션 일괄 추가에서는 그 타겟팅에 추가하는 새 최상위 옵션이 모두 실패합니다. | 콘솔에서 `detail.products`에 있는 광고 상품의 필수 옵션을 줄인 뒤 요청을 다시 보내세요. |
| `400` | `TARGETING_CATEGORY_LIMIT_EXCEEDED` | 타겟팅의 옵션이 하위 옵션을 포함해 100,000개를 넘게 됩니다. | 더 이상 쓰지 않는 옵션을 삭제하거나 다른 타겟팅을 사용하세요. |
| `400` | `TARGETING_CATEGORY_LOCKED` | 광고 상품에서 **필수 포함** 또는 **필수 제외**로 설정했거나 종료되지 않은 캠페인이 포함 또는 제외한 최상위 옵션을 옵션 그룹 안으로 옮기려 했습니다. | 먼저 그 설정을 해제하세요. 새 옵션을 옵션 그룹에 넣으려면 처음부터 `parent`나 `children`으로 추가하세요. [옵션 이동과 분리](/ko/integrations/context-targeting/option-groups#옵션-이동과-분리)를 참고하세요. |
| `400` | `TARGETING_CATEGORY_NOT_SUPPORTED` | 요청이 옵션의 유형이나 옵션 그룹 규칙에 맞지 않습니다. 이 표 아래의 경우를 참고하세요. | 요청을 고치세요. [제약 사항](/ko/integrations/context-targeting/option-groups#제약-사항)을 참고하세요. |
| `400` | `TARGETING_CATEGORY_TREE_ITEMS_LIMIT_EXCEEDED` | 옵션 일괄 삭제 요청에 옵션 그룹과 하위 옵션이 150개보다 많이 들어 있습니다. 요청 전체가 거부되며 아무것도 삭제되지 않습니다. | ID를 더 작은 요청으로 나눠 하나씩 차례로 보내세요. |
| `400` | `TARGETING_COLLECTION_CONTEXT_DUPLICATED` | 옵션 그룹의 자체 컨텍스트 ID로 추가하려는 컨텍스트 ID가 이미 프로젝트 안 다른 옵션 그룹의 자체 컨텍스트 ID입니다. 같은 요청에서 만들거나 변경하는 옵션 그룹도 포함됩니다. | 겹치는 컨텍스트 ID를 빼세요. `detail.conflicts`에 최대 20개까지 나열됩니다. 고유해야 하는 것은 옵션 그룹의 자체 컨텍스트 ID뿐입니다. 일반 옵션과 하위 옵션은 컨텍스트 ID가 겹쳐도 됩니다. |
| `400` | `TARGETING_CONTEXT_LIMIT_EXCEEDED` | 변경 후 컨텍스트 ID 한도를 넘게 됩니다. 한도는 옵션당 컨텍스트 ID 50,000개 또는 8 MB, 프로젝트당 컨텍스트 ID 200,000개입니다. | 더 이상 쓰지 않는 컨텍스트 ID를 삭제하세요. [한도](#한도)를 참고하세요. |
| `400` | `TARGETING_LOCKED` | 승인되었고 아직 종료되지 않은 캠페인이 옵션 중 하나를 포함하거나 제외하고 있는 타겟팅을 삭제하려 했습니다. 아무것도 삭제되지 않습니다. | 해당 캠페인이 종료된 뒤 타겟팅을 삭제하세요. |
| `404` | `PROJECT_NOT_FOUND` | API 키가 속한 프로젝트가 더 이상 존재하지 않습니다. | 존재하는 프로젝트에서 발급한 키를 사용하세요. |
| `404` | `TARGETING_CATEGORY_NOT_FOUND` | 옵션이 없거나 프로젝트의 문맥 타겟팅에 속하지 않거나, `parent`가 같은 타겟팅의 옵션이 아닙니다. 이미 성공한 삭제 요청을 다시 보내도 이 코드를 반환합니다. | ID를 확인하세요. 다시 보낸 삭제 요청이라면 옵션이 삭제된 것으로 처리하세요. |
| `404` | `TARGETING_NOT_FOUND` | 타겟팅이 없거나, 다른 프로젝트의 타겟팅이거나, 문맥 타겟팅이 아닙니다. 이미 성공한 삭제 요청을 다시 보내도 이 코드를 반환합니다. | ID를 확인하세요. 다시 보낸 삭제 요청이라면 타겟팅이 삭제된 것으로 처리하세요. |
| `409` | `CONTEXT_TARGETING_WRITE_IN_PROGRESS` | 같은 프로젝트의 다른 API 쓰기 요청이 처리 중이며, 요청의 어떤 내용도 적용되지 않았습니다. `results`에 이 오류가 있으면 그 항목의 어떤 내용도 적용되지 않은 것입니다. | `detail.retryAfterMs`밀리초만큼 기다린 뒤 다시 보내세요. [동시 쓰기](#동시-쓰기)를 참고하세요. |
| `409` | `TARGETING_CATEGORY_NAME_DUPLICATE` | 옵션 그룹과 하위 옵션을 포함해 타겟팅에 같은 이름의 옵션이 이미 있거나, 요청 안에서 이름이 겹칩니다. | 다른 이름을 사용하세요. 생성 요청을 다시 보낸 경우라면 `detail.existingIds`의 ID를 사용하세요. |
| `409` | `TARGETING_NAME_DUPLICATE` | 오디언스 타겟팅을 포함해 종류와 관계없이 프로젝트에 같은 이름의 타겟팅이 이미 있거나, 요청 안에서 이름이 겹칩니다. | 다른 이름을 사용하세요. 생성 요청을 다시 보낸 경우라면 `detail.existingId`의 ID를 사용하세요. |
| `500` | `UNCATCHED` | 일괄 작업의 `results`에만 나타납니다. 예기치 않은 오류가 발생했습니다. 생성·추가·삭제 일괄 요청에서는 요청의 어떤 항목도 적용되지 않았습니다. | 같은 요청을 다시 보내세요. 이름 변경·수정 일괄 요청이라면 실패한 항목을 다시 보내세요. |

`TARGETING_CATEGORY_NOT_SUPPORTED`는 다음 경우에 발생합니다.

* 옵션 그룹에 하위 옵션도 자체 컨텍스트 ID도 없는 경우
* `type: "atomic"`과 함께 `children`을 보내거나, 일반 옵션에 `exposeChildren`을 보낸 경우
* 옵션을 추가할 때 `parent`를 `type: "collection"`, `children`, `exposeChildren` 중 하나와 함께 보낸 경우
* `parent`가 최상위 옵션 그룹이 아닌 옵션을 가리키는 경우
* 수정 요청에서 옵션 그룹에 `parent`를 보낸 경우. 옵션 그룹은 옮길 수 없습니다.
* 이동, 분리, 삭제로 자체 컨텍스트 ID가 없는 옵션 그룹의 마지막 하위 옵션이 빠지게 되는 경우
* 수정으로 하위 옵션이 없는 옵션 그룹의 컨텍스트 ID가 모두 삭제되는 경우
* [옵션 목록 조회](/ko/integrations/context-targeting/options#옵션-목록-조회)의 `parent` 쿼리 파라미터가 일반 옵션을 가리키는 경우
* 옵션 그룹 안에 다른 옵션 그룹이 들어 있는 경우처럼, API로 변경할 수 없는 형태로 저장된 옵션인 경우

### detail 필드

| 필드 | 반환되는 오류 | 설명 |
| - | - | - |
| `existingId` | `TARGETING_NAME_DUPLICATE` | 그 이름을 쓰는 기존 타겟팅의 ID. 기존 타겟팅이 문맥 타겟팅일 때만 포함됩니다. |
| `existingIds` | `TARGETING_CATEGORY_NAME_DUPLICATE` | 타겟팅에서 그 이름을 쓰는 기존 옵션의 ID. 보통 하나지만, 타겟팅에 그 이름의 옵션이 이미 여러 개 있으면 여러 개입니다. |
| `duplicateOfIndex` | `TARGETING_NAME_DUPLICATE`, `TARGETING_CATEGORY_NAME_DUPLICATE` | 타겟팅 생성, 옵션 추가, 옵션 수정 요청 안에서 이름이 겹친 경우에 포함됩니다. 그 이름을 먼저 쓴 항목의 `index`이며, 한 항목 안에서 이름이 겹치면 그 항목 자신의 `index`입니다. 타겟팅 이름 변경 요청에서는 앞 항목의 이름 변경이 이미 적용된 상태이므로, 이름이 겹치면 이 필드 대신 `existingId`가 반환됩니다. |
| `retryAfterMs` | `CONTEXT_TARGETING_WRITE_IN_PROGRESS` | 요청을 다시 보내기 전에 기다릴 시간(밀리초). 0\~2,000 사이의 값입니다. |
| `targeting` | `TARGETING_CATEGORY_LIMIT_EXCEEDED` | 한도를 넘게 되는 타겟팅의 ID |
| `limit` | `TARGETING_CATEGORY_LIMIT_EXCEEDED`, `AD_PRODUCT_TARGETING_POLICY_LIMIT_EXCEEDED`, `TARGETING_CATEGORY_TREE_ITEMS_LIMIT_EXCEEDED` | 넘게 되는 한도 |
| `count` | `AD_PRODUCT_TARGETING_POLICY_LIMIT_EXCEEDED`, `TARGETING_CATEGORY_TREE_ITEMS_LIMIT_EXCEEDED` | 광고 상품 한도에서는 변경 후 광고 상품에 남게 되는 필수 옵션 수입니다. 여러 광고 상품이 한도를 넘으면 그중 가장 큰 값입니다. 일괄 삭제 한도에서는 요청에 포함된 옵션 그룹과 하위 옵션의 수입니다. |
| `products` | `AD_PRODUCT_TARGETING_POLICY_LIMIT_EXCEEDED` | 한도를 넘게 되는 광고 상품의 ID |
| `conflicts` | `TARGETING_COLLECTION_CONTEXT_DUPLICATED` | 겹친 항목(최대 20개). 각 항목에는 `value`(컨텍스트 ID), `collection`(다른 옵션 그룹의 `_id`와 `name`), `targeting`(그 옵션 그룹이 속한 타겟팅의 `_id`와 `name`)이 들어 있습니다. 그 옵션 그룹을 같은 요청에서 만들거나 변경했다면 `collection._id`, `targeting`, `targeting.name`이 빠질 수 있습니다. |

***

## 일괄 작업 응답

`POST /context-targeting/bulk/create`, `POST /context-targeting/category/bulk/update` 같은 일괄 작업 엔드포인트는 항목마다 결과를 반환합니다.

### 응답 필드

| 필드 | 타입 | 설명 |
| - | - | - |
| `results` | object\[] | 항목별 결과(요청 순서대로) |
| `results[].index` | number | 요청의 `items` 또는 `ids` 배열에서 항목의 위치(0부터 시작) |
| `results[].success` | boolean | 항목이 성공했으면 `true` |
| `results[].data` | object | 성공한 항목의 결과. 단건 엔드포인트가 반환하는 객체와 같습니다. 삭제는 `_id`만 들어 있습니다. |
| `results[].error` | object | 실패한 항목의 오류로, `statusCode`, `errorCode`와, 일부 코드에서는 `detail`이 들어 있습니다. [코드별 오류](#코드별-오류)를 참고하세요. |
| `succeeded` | number | 성공한 항목 수 |
| `failed` | number | 실패한 항목 수 |

### 응답 상태

일괄 요청이 처리되면 항목 일부나 전체가 실패해도 `200`을 반환합니다. `failed`나 각 항목의 `success`를 확인하세요.

다음 경우에는 요청 전체가 `results` 없이 실패하며, 요청의 어떤 내용도 적용되지 않습니다.

* `400` 검증 오류. 예를 들어 항목이 너무 많거나, 컨텍스트 ID가 모두 합쳐 10,000개를 넘거나, 옵션 일괄 수정에 같은 `_id`가 두 번 들어 있는 경우입니다.
* 옵션 일괄 삭제의 `400 TARGETING_CATEGORY_TREE_ITEMS_LIMIT_EXCEEDED`
* `401`, `403`, `413`
* `404 PROJECT_NOT_FOUND`
* `409 CONTEXT_TARGETING_WRITE_IN_PROGRESS`

`500`이나 `502` 응답에도 `results`가 없지만, 이때는 요청이 적용됐을 수도 있고 적용되지 않았을 수도 있습니다.

### 항목 적용 방식

| 일괄 요청 | 항목 적용 방식 |
| - | - |
| 타겟팅 생성, 타겟팅 삭제, 옵션 추가, 옵션 삭제 | 모든 항목을 하나의 단위로 한꺼번에 적용합니다. 이름 중복이나 한도 초과처럼 저장 전 검사에서 걸린 항목만 따로 실패하고, 나머지 항목은 적용됩니다. 예기치 않은 오류가 발생하면 요청의 어떤 항목도 적용되지 않습니다. 이때 이미 실패한 항목은 원래 오류를 그대로 받고, 나머지 항목은 모두 `500 UNCATCHED`로 실패합니다. 같은 요청을 다시 보내세요. |
| 타겟팅 이름 변경, 옵션 수정 | 항목마다 따로 적용합니다. 실패한 항목은 다른 항목에 영향을 주지 않으며, 성공한 항목은 적용된 상태로 남습니다. |

* 옵션 일괄 수정은 항목을 동시에 적용하지만, `results`는 요청 순서를 그대로 유지합니다. 한 옵션의 변경 내용은 모두 한 항목에 담으세요. 같은 `_id`가 두 번 들어 있으면 요청 전체가 거부됩니다.
* 이름 변경·수정 요청에서는 앞 항목이 이름을 바꾸면서 비운 이름을 뒤 항목이 쓸 수 있습니다. 앞 항목이 실패하면 뒤 항목은 이름 중복 오류로 실패합니다.

### 예시

첫 번째 항목은 **Sports**에 컨텍스트 ID를 추가합니다. 두 번째 항목은 **Tech**의 이름을 이미 사용 중인 **Sports**로 바꾸려고 합니다.

```bash theme={null}
curl -X POST https://lake.adrop.io/context-targeting/category/bulk/update \
  -H "x-api-key: YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "items": [
      { "_id": "01JAB3XK5RA1B2C3D4E5F6G7H8", "contexts": { "add": ["basketball"] } },
      { "_id": "01JAB3XK5RA1B2C3D4E5F6G7H9", "name": "Sports" }
    ]
  }'
```

응답 상태는 `200`입니다.

```json theme={null}
{
  "results": [
    {
      "index": 0,
      "success": true,
      "data": {
        "_id": "01JAB3XK5RA1B2C3D4E5F6G7H8",
        "name": "Sports",
        "type": "atomic",
        "contextCount": 4,
        "createdAt": "2026-10-13T01:23:45.000Z",
        "updatedAt": "2026-10-13T02:10:00.000Z"
      }
    },
    {
      "index": 1,
      "success": false,
      "error": {
        "statusCode": 409,
        "errorCode": "TARGETING_CATEGORY_NAME_DUPLICATE",
        "detail": {
          "existingIds": ["01JAB3XK5RA1B2C3D4E5F6G7H8"]
        }
      }
    }
  ],
  "succeeded": 1,
  "failed": 1
}
```

***

## 동시 쓰기

한 프로젝트의 API 쓰기 요청은 한 번에 하나씩 처리됩니다. 프로젝트의 어떤 API 키로 보냈든, 단건과 일괄을 가리지 않고 문맥 타겟팅 API의 모든 `POST`, `PATCH`, `DELETE` 요청이 여기에 해당합니다. `GET` 요청은 영향을 받지 않으며, 서로 다른 프로젝트의 쓰기 요청은 서로를 기다리지 않습니다.

프로젝트의 다른 쓰기 요청이 처리되는 중에 쓰기 요청이 도착하면, 서버는 앞 요청이 끝나기를 잠시 기다립니다. 그래도 끝나지 않으면 요청은 `409 CONTEXT_TARGETING_WRITE_IN_PROGRESS`로 실패하며, 요청의 어떤 내용도 적용되지 않습니다. 일괄 요청은 `results` 없이 전체가 실패합니다.

```json theme={null}
{
  "statusCode": 409,
  "errorCode": "CONTEXT_TARGETING_WRITE_IN_PROGRESS",
  "timestamp": "2026-10-13T10:23:45+09:00",
  "detail": {
    "retryAfterMs": 2000
  }
}
```

이 오류는 다음과 같이 처리하세요.

1. `detail.retryAfterMs`밀리초만큼 기다립니다. 값은 0\~2,000 사이입니다.
2. 같은 요청을 다시 보냅니다. 다시 `409`를 받으면 이 과정을 반복합니다.

한 프로젝트의 쓰기 요청은 순서대로 보내세요. 요청 하나의 응답을 받은 뒤에 다음 요청을 보내면 됩니다. 여러 프로세스나 워커가 같은 프로젝트에 쓴다면, 각각 `409`를 받았을 때 재시도해야 합니다.

<Note>
  드물게, 처리가 유난히 오래 걸리는 요청의 차례를 다른 요청이 넘겨받을 수 있습니다. 그러면 처음 요청이 아직 적용하지 못한 항목은 `results`에서 같은 `409`로 실패하며, 단건 엔드포인트에서는 이 오류가 응답으로 반환됩니다. 이 오류를 받은 항목은 적용되지 않았으므로 그 항목만 다시 보내세요.
</Note>

***

## 재시도와 중복 요청

요청이 타임아웃되거나 `502` 등 `5xx` 상태를 반환하면 요청이 적용됐는지 알 수 없습니다. 서버가 아직 원래 요청을 처리하고 있을 수도 있습니다. 잠시 기다린 뒤 같은 요청을 다시 보내세요. 원래 요청이 아직 처리 중이면 다시 보낸 요청은 `409 CONTEXT_TARGETING_WRITE_IN_PROGRESS`로 실패합니다. 이때는 기다렸다가 다시 보내세요.

이미 성공한 요청을 다시 보냈을 때의 결과는 작업에 따라 다릅니다.

| 작업 | 다시 보냈을 때의 결과 |
| - | - |
| 타겟팅 생성 또는 옵션 추가(단건·일괄) | 멱등(idempotent)하지 않습니다. 원래 요청이 만든 항목은 각각 이름 중복 오류로 실패하며, `detail`에 기존 ID가 들어 있습니다. |
| 타겟팅 또는 옵션 이름 변경 | 결과가 같습니다. 현재 이름을 그대로 보내도 오류가 아닙니다. |
| `add`, `remove`, `set`으로 컨텍스트 ID 변경 | 결과가 같습니다. `add`는 이미 있는 컨텍스트 ID를 건너뛰고, `remove`는 없는 컨텍스트 ID를 무시합니다. |
| `parent`로 옵션 이동·분리 | 결과가 같습니다. 이미 요청한 위치에 있는 옵션은 그대로 남습니다. |
| `exposeChildren` 변경 | 결과가 같습니다. |
| 타겟팅 또는 옵션 삭제 | `404 TARGETING_NOT_FOUND` 또는 `TARGETING_CATEGORY_NOT_FOUND`를 반환합니다. 삭제된 것으로 처리하세요. |

### 생성 요청을 다시 보낼 때

ID는 항상 서버가 생성하며, 생성 요청에 `_id`를 넣으면 `400`으로 실패합니다. 이미 성공한 생성 요청을 다시 보냈다면, 항목을 다시 만들지 말고 `409` 응답에 있는 ID를 사용하세요.

* **타겟팅**: `409 TARGETING_NAME_DUPLICATE`. `detail.existingId`가 그 타겟팅의 ID입니다. 옵션의 ID를 확인하려면 [타겟팅 조회](/ko/integrations/context-targeting/targetings#타겟팅-조회)를 참고하세요.
* **옵션**: `409 TARGETING_CATEGORY_NAME_DUPLICATE`. `detail.existingIds`에 그 이름을 쓰는 옵션의 ID가 들어 있습니다.
* 일괄 생성·추가 요청에서는 원래 요청이 만든 항목만 이렇게 실패합니다. 나머지 항목은 평소대로 처리됩니다.

`detail.existingId`는 기존 타겟팅이 문맥 타겟팅일 때만 포함됩니다. 오디언스 타겟팅이 그 이름을 쓰고 있다면 다른 이름을 고르세요. 같은 요청 안에서 이름이 겹치면 대신 `detail.duplicateOfIndex`가 반환됩니다. 이때는 요청을 고치세요.

한 프로젝트의 API 쓰기 요청은 한 번에 하나씩 처리되므로, 똑같은 생성 요청 두 개를 동시에 보내도 둘 다 생성되지는 않습니다. 나중에 처리되는 요청은 이름 중복 오류나 `409 CONTEXT_TARGETING_WRITE_IN_PROGRESS`로 실패합니다.

### 500이나 502를 받은 경우

* 생성·추가·삭제 일괄 요청의 `results`에 `500 UNCATCHED`가 있는 경우: 요청의 어떤 항목도 적용되지 않았습니다. 같은 요청을 다시 보내세요.
* 이름 변경·수정 일괄 요청의 `results`에 `500 UNCATCHED`가 있는 경우: 그 항목이 실패한 것입니다. 그 항목을 다시 보내세요.
* `500`이나 `502` 응답을 받은 경우: 요청이 적용됐을 수도 있고 적용되지 않았을 수도 있습니다. 같은 요청을 다시 보내고, 결과는 위 표에 따라 처리하세요.

<Note>
  `add`와 `remove`를 함께 보낸 옵션 수정이 실패하면 `add`는 이미 적용됐을 수 있습니다. 같은 수정 요청을 다시 보내도 안전합니다.
</Note>

***

## 한도

| 한도 | 값 | 초과 시 |
| - | - | - |
| 타겟팅 일괄 요청(생성, 이름 변경, 삭제)당 항목 수 | 1\~100 | `400` 검증 오류 |
| 타겟팅 생성 요청당 옵션 수(하위 옵션 포함, 모든 항목 합계) | 100 | `400` 검증 오류 |
| 옵션 일괄 요청(추가, 수정, 삭제)당 항목 수 | 1\~5,000 | `400` 검증 오류 |
| 옵션 추가 요청당 옵션 수(하위 옵션 포함, 모든 항목 합계) | 5,000 | `400` 검증 오류 |
| 옵션 일괄 요청당 옵션 그룹 관련 항목 수: 옵션 그룹을 만들거나 `parent`가 있는 항목(추가), `null`을 포함해 `parent`가 있는 항목(수정), 옵션 그룹과 하위 옵션의 ID(삭제) | 150 | 추가·수정은 `400` 검증 오류, 삭제는 `400 TARGETING_CATEGORY_TREE_ITEMS_LIMIT_EXCEEDED` |
| 요청당 컨텍스트 ID 수(`add`, `remove`, `set`을 포함해 모든 항목 합계) | 10,000 | `400` 검증 오류 |
| 컨텍스트 ID 길이(앞뒤 공백을 제거한 뒤) | UTF-8 기준 1\~256바이트 | `400` 검증 오류 |
| 요청 본문 크기 | 8 MB | `413` |
| 옵션당 컨텍스트 ID 수(옵션 그룹은 자체 컨텍스트 ID) | 50,000 | `400 TARGETING_CONTEXT_LIMIT_EXCEEDED` |
| 옵션의 컨텍스트 ID 전체 크기(옵션 그룹은 자체 컨텍스트 ID) | 8 MB | `400 TARGETING_CONTEXT_LIMIT_EXCEEDED` |
| 프로젝트당 컨텍스트 ID 수(모든 문맥 타겟팅의 모든 옵션 합계) | 200,000 | `400 TARGETING_CONTEXT_LIMIT_EXCEEDED` |
| 타겟팅당 옵션 수(하위 옵션 포함) | 100,000 | `400 TARGETING_CATEGORY_LIMIT_EXCEEDED` |
| 광고 상품당 **필수 포함** 또는 **필수 제외**로 설정한 옵션 수(모든 타겟팅 합계) | 500 | `400 AD_PRODUCT_TARGETING_POLICY_LIMIT_EXCEEDED` |
| 타겟팅 이름과 옵션 이름의 길이 | 2\~36자 | `400` 검증 오류 |
| [옵션 목록 조회](/ko/integrations/context-targeting/options#옵션-목록-조회)의 `limit` | 1\~1,000(기본값 1,000) | `400` 검증 오류 |
| [옵션 조회](/ko/integrations/context-targeting/options#옵션-조회)의 `limit` | 1\~10,000(기본값 10,000) | `400` 검증 오류 |
| 옵션 조회의 `offset` | 0\~50,000(기본값 0) | `400` 검증 오류 |
| 옵션 그룹의 `children`에 담기는 하위 옵션 ID | 처음 50개. 전체 수는 `childCount`에 있습니다. | — |
| `detail.conflicts`에 담기는 항목 | 최대 20개 | — |

* 저장된 데이터에 대한 한도(옵션당, 프로젝트당, 타겟팅당, 광고 상품당)는 변경으로 양이 늘어날 때만 확인합니다. 컨텍스트 ID 삭제처럼 양을 줄이는 변경은 데이터가 이미 한도를 넘은 상태여도 막히지 않습니다.
* 요청 하나에 담을 수 있는 양보다 많이 올리려면 이 한도 안에서 요청을 나눠 하나씩 차례로 보내세요. 예를 들어 옵션 수만 개는 항목 5,000개 이하의 옵션 일괄 추가 요청으로 나눠 추가합니다.
* 컨텍스트 ID가 10,000개를 넘는 옵션의 컨텍스트 ID를 교체하려면 [컨텍스트 ID 갱신 방법](/ko/integrations/context-targeting/options#컨텍스트-id-갱신-방법)을 참고하세요.
* 요청 본문을 작게 유지하려면 한글 이름이나 컨텍스트 ID 같은 비ASCII 문자를 UTF-8 그대로 보내세요.

***

## 이름 규칙

타겟팅 이름과 옵션 이름(옵션 그룹과 하위 옵션 포함)에는 다음 규칙이 적용됩니다.

| 규칙 | 타겟팅 | 옵션 |
| - | - | - |
| 길이 | 2\~36자 | 2\~36자 |
| 허용 문자 | 한글, 영문, 숫자, 공백, `~` `%` `&` `+` `-` | 타겟팅과 같음 |
| 앞뒤 공백 | 허용되지 않습니다. API는 공백을 제거해 주지 않고 이름을 거부합니다. | 타겟팅과 같음 |
| 고유해야 하는 범위 | 프로젝트(오디언스 타겟팅을 포함한 모든 타겟팅) | 타겟팅(옵션 그룹과 하위 옵션 포함) |
| 이름이 규칙에 맞지 않을 때의 오류 | `400` 검증 오류 | `400` 검증 오류 |
| 이름이 중복될 때의 오류 | `409 TARGETING_NAME_DUPLICATE` | `409 TARGETING_CATEGORY_NAME_DUPLICATE` |

* 일본어나 중국어 문자, `_`, `.`, 괄호 등 그 밖의 문자는 허용되지 않습니다.
* 요청 하나 안에서 겹치는 이름도 중복입니다. [detail 필드](#detail-필드)의 `duplicateOfIndex`를 참고하세요.
* 현재 이름으로 이름을 변경해도 오류가 아닙니다.
* 서로 다른 타겟팅에 있는 옵션은 이름이 같아도 됩니다.

***

## 콘솔과 함께 쓸 때

* **같은 데이터**: 콘솔과 API는 같은 문맥 타겟팅을 관리합니다. API로 만든 타겟팅과 옵션은 콘솔에 표시되고, 콘솔에서 만든 타겟팅과 옵션도 API로 조회됩니다.
* **문맥 타겟팅만 지원**: 오디언스 타겟팅에 대한 요청은 `404 TARGETING_NOT_FOUND`로, 그 옵션에 대한 요청은 `404 TARGETING_CATEGORY_NOT_FOUND`로 실패합니다. 단, 이름이 고유한지 확인할 때는 오디언스 타겟팅의 이름도 포함되므로, 같은 이름으로 문맥 타겟팅을 만들면 `detail.existingId` 없이 `409 TARGETING_NAME_DUPLICATE`로 실패합니다.
* **콘솔 편집은 순차 처리에서 제외**: 한 번에 하나씩 처리하는 방식은 API 쓰기 요청에만 적용됩니다. 콘솔 편집은 API 쓰기를 기다리지 않고 `409 CONTEXT_TARGETING_WRITE_IN_PROGRESS`를 일으키지도 않으며, API 쓰기도 콘솔 편집을 기다리지 않습니다.
* **동시 변경 시 충돌 가능**: 콘솔과 API가 같은 타겟팅을 동시에 변경하면 나중에 저장된 변경이 앞선 변경을 덮어쓸 수 있고, 두 타겟팅이나 두 옵션의 이름이 같아질 수 있습니다. API 연동이 타겟팅을 변경하는 동안에는 콘솔에서 그 타겟팅을 편집하지 마세요. 콘솔에서 변경됐을 수 있다면 쓰기 전에 타겟팅을 다시 조회하세요.
* **중복된 이름**: 두 타겟팅이나 두 옵션의 이름이 같아졌다면 그중 하나의 이름을 바꾸거나 하나를 삭제하세요. 옵션을 삭제하기 전에는 [광고 상품과 캠페인에 미치는 영향](/ko/integrations/context-targeting/options#광고-상품과-캠페인에-미치는-영향)을 확인하세요.
* **광고 상품 설정**: 광고 상품에서 어떤 옵션을 **필수 포함** 또는 **필수 제외**로 설정할지는 콘솔에서 바꾸세요. [타겟팅 판매하기](/ko/targeting/sell)를 참고하세요.


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