# LLM 코딩 에이전트용 퍼블리셔 공용 API 마이그레이션 레퍼런스

> Monetize Manage API (legacy)에서 Ads Unified Platform 공용 API로 클라이언트의 통합을 마이그레이션하는 LLM 코딩 에이전트에 대한 레퍼런스입니다.

> **잠재 고객:** 이 파일은 LLM 코딩 에이전트(Claude Code 또는
> similar)는 **레거시** Unity "Monetize"에서 클라이언트 통합을 마이그레이션합니다.
> **새로운** Unity Ads 퍼블리셔 공용 API 관리(`/monetize/v1/...`)
> (`/ads/publisher/public/v1/...`). 그것은 독립적인 엔티티 블록으로 분할됩니다. 각 차단
> 상태 *변화* 및 *정확히 어떻게 해야 하는*. **개념 모델 변경** 및
> 먼저 **전역 변경** 섹션 - 모든 차단에 적용됩니다.

## 이 파일을 사용하는 방법(에이전트 지침)##how-to-use-this-file-(instructions-for-the-agent)

1. 이 프로시가 아니라 두 계약을 진실의 근원으로 취급합니다. 레거시 =
   `ads-selfserve/src/servers/http-internal/openapi/public-v1.yaml`. New =
   `ads-unified-platform/openapi/publisher/platform-public-v1-routes.yaml`.
2. 지정된 순서로 한 번에 **한 엔티티 차단**을 마이그레이션합니다(애플리케이션 → 플레이스먼트 →
   org 레벨 목록). 의존 중인 블록이 컴파일/패스하기 전에는 차단 시작하지 않습니다.
3. 레거시 기능이 **± No equivalent** 아래에 나열되면\*\*\*\*
   엔드포인트 중지하고 인간에게 열린 질문으로 표면. 가장 중요 예제
   는 새로운 API에 존재하지 않는 **eCPM 타겟**입니다.
4. 각 차단 후 필드 테이블별로 필드 이름을 다시 매핑합니다. 필드가 이동된 것으로 간주하지 마십시오.
   표에 "unchanged"라고 표시되지 않는 한 변경되지 않습니다.

***

## 개념 모델 변경(첫 번째 읽기)##conceptual-model-change-(read-this-first)

데이터 모델이 평평화되고 **프로젝트**에서 **애플리케이션**으로 재중심화되었습니다.

### 레거시 모델(중첩, 프로젝트 중심)##legacy-model-(nested-project-centric)

```xml
조직
└── 프로젝트(두 스토어 모두 포함, coppa + adsProvider 포함)
    └── 스토어 (apple | google) (URL 경로)
        └── Ad-Unit(adFormat, 건너뛰기/음소거/배너 설정 라이브)
            └── 플레이스먼트
                └── eCPM 타겟(기하 기반 단가)
```

### 새로운 모델(플랫, 애플리케이션 중심)##new-model-(flat-application-centric)

```xml
조직
└── 애플리케이션(플랫폼 하나 + 각각 하나의 스토어, 개인정보 보호 기능 + testMode 포함)
    └── 플레이스먼트(adFormat + adFormatConfigurations는 여기에 라이브, 타겟 없음)
조직
└── 테스트 기기(새로운, 조직 범위)
```

#### 모든 변환, 변형 유도하는 4가지 쉬프트##the-four-shifts-that-drive-every-transformation

| **#** | **Shift**         | **마이그레이션의 결과**                                                                                                                                                 |
| ----- | ----------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| 1     | **프로젝트 → 애플리케이션** | 레거시 *프로젝트* 에는 **`apple` 및 `google` 스토어**가 모두 있으면 **두 애플리케이션**이 됩니다(플랫폼당 하나). "프로젝트"는 여전히 선택적 그룹화(앱 생성 시 `projectId`/`projectName`)로 존재하지만 더 이상 운영되는 리소스가 아닙니다. |
| 2     | **Ad-Units 제거**   | 광고 유닛 리소스가 없습니다. 애드 유닛 레벨 설정(건너뛰기 타이머, 음소거, 배너 새로고침)은 **placement**로 `adFormatConfigurations`로 이동했습니다. 이제 `adFormat`는 **placement** 프로퍼티입니다.                   |
| 3     | **eCPM 타겟 제거**    | 새로운 공용 API 해당하는 항목이 없습니다.                                                                                                                                      |
| 4     | **배치 → 개별 작업**    | 레거시 생성/업데이트된 플레이스먼트와 광고 유닛 일괄(맵/배열) 새로운 API는 요청당 하나의 리소스(`POST`/`PUT`/`DELETE` 단일 플레이스먼트)입니다.                                                                  |

## 전역 변경 사항(모든 엔드포인트에 적용)##global-changes-(apply-to-all-endpoints)

| **종횡비**                   | **레거시**                                        | **신규**                                                                                                                |
| ------------------------- | ---------------------------------------------- | --------------------------------------------------------------------------------------------------------------------- |
| **호스트**                   | `https://services.api.unity.com`               | `https://services.api.unity.com/ads/publisher` (prod) · `https://staging.services.api.unity.com/ads/publisher` (스테이징) |
| **경로 접두어**                | `/monetize/v1`                                 | `/public/v1`                                                                                                          |
| **전체 예제**                 | `…/monetize/v1/organizations/{orgId}/projects` | `…/ads/publisher/public/v1/organizations/{organizationId}/applications`                                               |
| **Auth**                  | HTTP **Basic**(서비스 계정 키)                       | HTTP **Basic**은 계속 작동하며 Bearer JWT(`unity`/`ads` 발급자)도 허용됩니다. **변경 필요 없음** — 기존의 Basic 자격 증명을 유지합니다.                  |
| **ORG ID**                | 경로의 `organizationId`(숫자 문자열)                   | `organizationId`(창세기 ID, 숫자) — **변경되지 않음**                                                                            |
| **경로에 있는 기본 ID**          | `projectId`(uuid **또는** 숫자)                    | `applicationId`(Unity 앱 ID, 문자열)                                                                                      |
| **`dryrun`쿼리 파라미터**       | 쓰기에서 지원됨                                       | 거의 제거된                                                                                                                |
| **`showarchived`쿼리 파라미터** | 지원됨                                            | 배치 목록에 있는 `isArchived`(부울)로 대체됨                                                                                       |
| **아카이빙**                  | 패치 바디의 `archive: true/false` 필드                | 전용 `DELETE`(아카이브) + `PATCH …/restore`(아카이브 제외)                                                                        |

### 속도 제한이 변경됨(클라이언트 측 백오프 가정 업데이트)##rate-limits-changed-(update-any-client-side-backoff-assumptions)

|                              | **레거시**       | **신규**               |
| ---------------------------- | ------------- | -------------------- |
| IP당 천장                       | 5/s, 4000/30분 | **40/s**             |
| 읽기(`GET`)                    | (전역)          | **20/s + 8000/hour** |
| 생성(`POST`)                   | 5/s, 1000/30분 | **1/s + 60/hour**    |
| 업데이트(`PATCH`/`PUT`/`DELETE`) | 5/s, 1000/30분 | **1/s + 200/hour**   |

## 마스터 엔드포인트 맵##master-endpoint-map

| **레거시 작업**                                                                                                             | **레거시 경로**                                                         | **신규 작업**                                                          | **새로운 경로**                                                                      | **차단** |
| ---------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------ | ------------------------------------------------------------------ | ------------------------------------------------------------------------------- | ------ |
| `getProjects`                                                                                                          | `GET /monetize/v1/organizations/{orgId}/projects`                  | `publicOrganizationApplications`                                   | `GET /public/v1/organizations/{orgId}/applications`                             | A      |
| `postProjects`                                                                                                         | `POST /monetize/v1/organizations/{orgId}/projects`                 | `publicCreateApplication`                                          | `POST /public/v1/organizations/{orgId}/applications`                            | A      |
| `getProjectDetails`                                                                                                    | `GET /monetize/v1/projects/{projectId}`                            | `publicGetApplicationById`                                         | `GET /public/v1/organizations/{orgId}/applications/{applicationId}`             | A      |
| `updateProjectDetails`                                                                                                 | `PATCH /monetize/v1/projects/{projectId}`                          | `publicUpdateApplication`                                          | `PATCH /public/v1/organizations/{orgId}/applications/{applicationId}`           | A      |
| `getAdUnitsByProject` / `getAdUnitsByProjectStore` / `createAdUnits` / `updateAdUnits` / `getAdUnits` / `patchAdUnits` | `…/projects/{projectId}/[stores/{storeName}/]adunits[/{adUnitId}]` | —                                                                  | 에서 광고 유닛 리소스 없음 보고                                                              | B      |
| `createPlacements`(배치)                                                                                                 | `POST …/adunits/{adUnitId}/placements`                             | `publicCreatePlacement`(단일)                                        | `POST /public/v1/organizations/{orgId}/applications/{applicationId}/placements` | C      |
| `GetPlacements` (ad-unit)                                                                                              | `GET …/adunits/{adUnitId}/placements`                              | `publicApplicationPlacements`                                      | `GET …/applications/{applicationId}/placements`                                 | C      |
| `getPlacement`                                                                                                         | `GET …/placements/{placementId}`                                   | `publicGetPlacementDetails`                                        | `GET …/applications/{applicationId}/placements/{placementId}`                   | C      |
| `updatePlacements`(배치) / `updatePlacement`                                                                             | `PATCH …/placements[/{placementId}]`                               | `publicUpdatePlacement`(단일, **PUT**)                               | `PUT …/applications/{applicationId}/placements/{placementId}`                   | C      |
| (아카이브 via `archive:true`)                                                                                              | `PATCH …/placements/{placementId}`                                 | `publicArchivePlacement`                                           | `DELETE …/applications/{applicationId}/placements/{placementId}`                | C      |
| (un-아카이브 via `archive:false`)                                                                                          | `PATCH …/placements/{placementId}`                                 | `publicUnArchivePlacement`                                         | `PATCH …/applications/{applicationId}/placements/{placementId}/restore`         | C      |
| `GetPlacements` (org)                                                                                                  | `GET /monetize/v1/organizations/{orgId}/placements`                | `publicOrganizationPlacements`                                     | `GET /public/v1/organizations/{orgId}/placements`                               | D      |
| `getTargets` / `createTargets` / `getTargetDetail` / `patchTarget` / `deleteTarget`                                    | `…/placements/{placementId}/targets[/{targetId}]`                  | —                                                                  |                                                                                 | E      |
| —                                                                                                                      | —                                                                  | `publicGetApplicationTestMode` / `publicUpdateApplicationTestMode` | `GET`/`PATCH …/applications/{applicationId}/test-mode`                          | F(새로운) |
| —                                                                                                                      | —                                                                  | 시험 기기 CRUD                                                         | `…/organizations/{orgId}/test-devices[/{testDeviceId}]`                         | G(새로운) |

## 차단 A - 프로젝트 → 응용 프로그램##block-a---projects--applications

### 변경한 사항##what-changed

* 나열/생성/읽기/업데이트하는 리소스는 이제 프로젝트가 아니라 **애플리케이션**입니다.
* 읽기/업데이트는 이제 org(`/organizations/{orgId}/applications/{applicationId}`) 아래에 중첩됩니다.
  스탠드얼론 `/projects/{projectId}`가 아닌 **`applicationId`**(Unity App ID)가 키를 입력합니다.
* 레거시 프로젝트는 **둘 다** 저장소를 가져왔습니다. 새로운 애플리케이션은 **단일 플랫폼**입니다. 분할
  변환하다(동사), 트랜스폼(명사) 규칙을 참조하십시오.
* `adsProvider`가 사라졌습니다. 열거형 문자열에서 부울로 변경된 `coppa`는
  새로운 `mixedAudience` 부울 옆의 `privacy` 오브젝트

### 엔드포인트 매핑##endpoint-mapping

| **레거시**                                 | **신규**                                                       |
| --------------------------------------- | ------------------------------------------------------------ |
| `GET …/organizations/{orgId}/projects`  | `GET …/organizations/{orgId}/applications`                   |
| `POST …/organizations/{orgId}/projects` | `POST …/organizations/{orgId}/applications`                  |
| `GET …/projects/{projectId}`            | `GET …/organizations/{orgId}/applications/{applicationId}`   |
| `PATCH …/projects/{projectId}`          | `PATCH …/organizations/{orgId}/applications/{applicationId}` |

### 필드 매핑 — **생성** (`createProject` → `CreateApplicationPublicDto`)##field-mapping--**create**-(createproject--createapplicationpublicdto)

| **레거시 필드**                                               | **새 필드**                                  | **참고**                                                           |
| -------------------------------------------------------- | ----------------------------------------- | ---------------------------------------------------------------- |
| `name`(필수)                                               | `name` (필수)                               | 변경되지 않음                                                          |
| `adsProvider` (필수, 열거형)                                  | —                                         | **Dropped** 전송하지 않습니다. 동등하지 않습니다.                                |
| `coppa` (필수, `"compliant"`/`"non_compliant"`)            | `privacy.coppa` (부울)                      | 변환: `"compliant"` → `true`, `"non_compliant"` → `false`          |
| —                                                        | `privacy.mixedAudience` (부울)              | **신제품.**`privacy` 내부에 필요합니다. 클라이언트에게 달리 알려지지 않는 한 기본 `false`입니다. |
| `stores.apple` / `stores.google` (`{storeId, storeUrl}`) | `store` + 열거형 `storeId` + 열거형 `platform`  | **플랫폼별 앱으로 분할** 변환하다(동사), 트랜스폼(명사) 규칙을 참조하십시오.                   |
| —                                                        | `platform` (필수, 열거형: `iOS`,`Android`,...) | **신규, 필수** 앱 타겟을 저장하는 데 파생됩니다.                                   |
| —                                                        | `iconUrl` (uri)                           | 새로운 옵션                                                           |
| —                                                        | `projectId` (uuid)                        | 선택 사항 — 기존 프로젝트와 연결하기                                            |
| —                                                        | `projectName` (문자열)                       | 선택 사항 - 새 프로젝트를 만들고 연결합니다. 기본값은 앱 이름입니다                          |

### 필드 매핑 — **업데이트**(`updateProject` → `UpdateApplicationPublicDto`)##field-mapping--**update**-(updateproject--updateapplicationpublicdto)

| **레거시 필드**    | **새 필드**             | **참고**                                                   |
| ------------- | -------------------- | -------------------------------------------------------- |
| `adsProvider` | —                    | 파티가 중단됨                                                  |
| `coppa`( 열거형) | `privacy.coppa` (부울) | 동일한 변환하다(동사), 트랜스폼(명사); 업데이트 시 두 `privacy.*` 필드 모두 선택 사항 |
| `stores`      | `store` + `storeId`  | 한 개의 플랫폼                                                 |
| —             | `name`               | 현재 업데이트 가능                                               |
| —             | `kidsSettings` (부울)  | new; `true`는 다운스트림 coppa=true 업데이트를 트리거합니다               |

### 필드 매핑 — **응답** (`project`/`projectDetail` → `ApplicationPublicResponseDto`)##field-mapping--**response**-(project/projectdetail--applicationpublicresponsedto)

| **레거시 필드**                   | **새 필드**                              | **참고**                                                      |
| ---------------------------- | ------------------------------------- | ----------------------------------------------------------- |
| `id` (project ID)            | `id`(애플리케이션 ID) + `projectId`         | 이제 응답에는 그룹화 프로젝트에 대한 **애플리케이션**`id`와 별도의 `projectId`가 있습니다. |
| `name`                       | `name`                                | 변경되지 않은                                                     |
| `coppa`( 열거형)                | `coppa`(부울) + `mixedAudience`(부울)     | enum→boolean                                                |
| `adsProvider`                | —                                     | ↓ 없어짐                                                       |
| `stores` (맵 of apple/google) | `store` + `storeId` + `platform`      | 단일 저장소                                                      |
| `archivedAt`                 | —                                     | 없음                                                          |
| `maxCaps`                    | —                                     | 비록 사라졌습니다                                                   |
| —                            | `iconUrl`, `testMode`, `kidsSettings` | new                                                         |

### 변환하다(동사), 트랜스폼(명사): 하나의 레거시 프로젝트 → N 응용 프로그램##transform-rule:-one-legacy-project--n-applications

`stores.apple` **및** `stores.google`가 있는 레거시 프로젝트의 경우, **두 개의** `POST /applications`를 내보냅니다.
호출 — 한 개는 `platform: iOS`, `store: AppleAppStore`, `storeId: <apple.storeId>`, 한 개는
`platform: Android`, `store: GooglePlay`, `storeId: <google.storeId>`. 를 그룹화하려면
단일 프로젝트, `projectName: <project.name>`로 첫 번째 앱을 만들고, 반환된
`projectId`에서 해당 `projectId`로 두 번째 앱을 만듭니다.

### 예제##example

**레거시 — create project (both stores):**

```json
POST /monetize/v1/organizations/3573617062594/projects
{
  "name": "내 게임",
  "adsProvider": "unity",
  "coppa": "non_compliant",
  "stores": {
    "apple":  { "storeId": "1479198816", "storeUrl": "https://apps.apple.com/..." },
    "google": { "storeId": "com.unity.trashdash", "storeUrl": "https://play.google.com/..." }
  }
}
```

**새로운 — 하나의 프로젝트에 그룹화된 두 개의 애플리케이션 생성:**

```json
POST /ads/publisher/public/v1/organizations/3573617062594/applications
{
  "name": "내 게임",
  "platform": "iOS",
  "store": "AppleAppStore",
  "storeId": "1479198816",
  "privacy": { "coppa": false, "mixedAudience": false },
  "projectName": "내 게임"
}
// → response.projectId = "5a8591dd-..."; reuse it for the second app:
POST /ads/publisher/public/v1/organizations/3573617062594/applications
{
  "name": "내 게임",
  "platform": "Android",
  "store": "GooglePlay",
  "storeId": "com.unity.trashdash",
  "privacy": { "coppa": false, "mixedAudience": false },
  "projectId": "5a8591dd-..."
}
```

***

## 차단 B — Ad-Units → 제거(애플리케이션 + 플레이스먼트로 접히기)##block-b--ad-units--removed-(folded-into-application-+-placement)

### □ 동등한 리소스 없음##-no-equivalent-resource

광고 유닛 엔드포인트는 **없습니다**. 호출하는 모든 클라이언트 코드를 삭제합니다.
`…/projects/{projectId}/[stores/{storeName}/]adunits…` (`getAdUnitsByProject`,
`getAdUnitsByProjectStore`, `createAdUnits`, `updateAdUnits`, `getAdUnits`, `patchAdUnits`).

### 광고 유닛 데이터가 이동한 위치##where-ad-unit-data-went

| **레거시 유닛 광고 필드**                                | **새로운 집**                                                                     |
| ----------------------------------------------- | ----------------------------------------------------------------------------- |
| `adFormat` (`rewarded`/`interstitial`/`banner`) | **플레이스먼트**`adFormat`(현재 플레이스먼트별).                                             |
| `allowSkipInSeconds`, `muteVideo`               | 플레이스먼트 `adFormatConfigurations.adminSettings.{allowSkipInSeconds, muteVideo}` |
| `bannerRefreshRate`                             | 배치 `adFormatConfigurations.bannerRefreshRate`(배너 전용)                          |
| `adTypes` (`display`/`playable`/`video`)        |  는 직접적으로 동등하지 않습니다. 표면은 클라이언트가 의지했다면 인간에 해당합니다.                               |
| 중첩된 `placements` 맵                              | 애플리케이션에서 개별적으로 플레이스먼트를 생성합니다(차단 C).                                           |

**마이그레이션 방식:** 각 레거시 광고 유닛을 *공유된 플레이스먼트 그룹으로 취급합니다.
adFormat*。 레거시 ad 유닛에 속한 모든 플레이스먼트의 경우,
**애플리케이션**(광고 유닛의 스토어와 일치하는 애플리케이션), 광고 유닛의 `adFormat`를
광고 유닛의 관리자 설정을 배치하고 플레이스먼트의 `adFormatConfigurations`로 접습니다.

***

## 차단 C — 플레이스먼트(애플리케이션별)##block-c--placements-(per-application)

### 변경한 사항##what-changed

* 경로가 이제 `…/applications/{applicationId}/placements[/{placementId}]` — 스토어/다음 단위 없음
  세그먼트。
* **개별** 작업은 배치 맵/배열을 대체합니다.
* 각 플레이스먼트에 대한 **`adFormat`가 필요**(유닛 프로퍼티였음).
* 새 **`adFormatConfigurations`** 오브젝트는 플레이어/관리자 설정을 포함합니다. 모양은
  `adFormat` (보상형 광고/인터스티셜 광고/배너).
* 배치 바디에서 **eCPM `targets`가 사라집니다**(차단 E 참조).
* 식별자: 레거시 `placementId`는 슬러그(`^[\da-zA-Z_-]+$`); 새로운 배치 `id`는
  **UUID**, 인간이 읽을 수 있는 슬러그는 별도로 **`key`**(`name`에서 생성)로 반환됩니다.
* 복원그 아닌 전용 작업입니다.
* 플레이스먼트별 **업데이트는 (전체 대체)이며`PUT`**, 아닙니다`PATCH`.

### 엔드포인트 매핑##endpoint-mapping

| **Intent** | **레거시**                                    | **신규**                                                                           |
| ---------- | ------------------------------------------ | -------------------------------------------------------------------------------- |
| 생성         | `POST …/adunits/{adUnitId}/placements`(배열) | `POST …/applications/{applicationId}/placements`(한)                              |
| 목록         | `GET …/adunits/{adUnitId}/placements`      | `GET …/applications/{applicationId}/placements` (필터: `isArchived`, `adFormat[]`) |
| 받으세요       | `GET …/placements/{placementId}`           | `GET …/applications/{applicationId}/placements/{placementId}`                    |
| 업데이트       | `PATCH …/placements/{placementId}`(또는 배치)  | `PUT …/applications/{applicationId}/placements/{placementId}`                    |
| 아카이브       | `PATCH …` 와 `{"archive": true}`            | `DELETE …/placements/{placementId}` (204)                                        |
| 아카이브       | `PATCH …` 와 `{"archive": false}`           | `PATCH …/placements/{placementId}/restore`                                       |

### 필드 매핑 — **요청** (`addPlacements[]` / `patchPlacementObject` → `PlacementPublicRequestDto`)##field-mapping--**request**-(addplacements-/-patchplacementobject--placementpublicrequestdto)

| **레거시 필드**                      | **새 필드**                                                | **참고**                                               |
| ------------------------------- | ------------------------------------------------------- | ---------------------------------------------------- |
| `name`(필수)                      | `name` (필수)                                             | 변경되지 않음                                              |
| `id` (슬러그, 생성 시)                | —                                                       | 더 이상 ID를 선택하지 않습니다. 서버는 UUID `id`와 `key` 슬러그를 반환합니다. |
| —                               | `adFormat` (필수, 열거형 `rewarded`/`interstitial`/`banner`) | **신규 및 필수** 레거시 부모 ad 유닛의 `adFormat`에서 출처.           |
| `addTargets` / `targets` (eCPM) | —                                                       |  님이 삭제되었습니다. 전송하지 않습니다. 차단 E.                        |
| `placementType`                 | —                                                       | 작문으로 수락 안 함                                          |
| `archive` (부울)                  | —                                                       | 대신 `DELETE`/`restore` 을 사용하십시오                       |
| —                               | `adFormatConfigurations`                                | **신규.** 플레이어/관리자 설정, 아래 모양을 참조하십시오.                  |

### `adFormatConfigurations` 모양(`adFormat` 기준)##adformatconfigurations-shape-(by-adformat)

* **보상형** 광고(`RewardedConfigurations`): `name` (보상 이름, 필수), `value` (보상형 광고,
  required), `adminSettings` (`AdminConfigurations`).
* **인터스티셜 광고** (`InterstitialConfigurations`): `adminSettings`.
* **배너** (`BannerConfigurations`): `adminSettings`, `bannerRefreshRate` (필수).
* **`AdminConfigurations`**: `allowSkip`, `allowSkipInSeconds`, `videoPlayableSkipInSeconds`,
  `closeTimerDuration`, `tapsToClose`, `muteVideo`, `disableVideoControlsFade`,
  `useCloseIconInsteadOfSkipIcon`.

### 필드 매핑 — **응답** (`placement` → `PlacementPublicResponseDto`)##field-mapping--**response**-(placement--placementpublicresponsedto)

| **레거시 필드**      | **새 필드**                                                                                                      | **참고**                                    |
| --------------- | ------------------------------------------------------------------------------------------------------------- | ----------------------------------------- |
| `id` (슬러그)      | `key` (슬러그)                                                                                                   | 레거시 슬러그 스타일 ID는 새로운 \*\*`key`\*\*에 매핑됩니다. |
| —               | `id` (uuid)                                                                                                   | **새로운** 규칙적 ID입니다. 이 ID는 URL 경로에 들어갑니다.   |
| `name`          | `name`                                                                                                        | 변경되지 않은                                   |
| `archivedAt`    | `archivedAt`(날짜-시간, nullable)                                                                                 | 현재 ISO-8601; 활성화된 경우 `null`               |
| `placementType` | —                                                                                                             | 앱별 응답에 표시되지 않음(조직 목록, 차단 D)               |
| `targets`       | —                                                                                                             | 가 거의 제거되었습니다                              |
| —               | `adFormat`, `status` (`active`/`paused`), `adFormatConfigurations`, `applicationId`, `createdAt`, `updatedAt` | new                                       |

> ⚡️ **ID 다시 매핑은 가장 높은 위험 단계입니다.** 클라이언트 레거시 플레이스먼트를 유지한 위치
> 슬러그는 이제 경로 작업을 위해 새 UUID `id`를 저장해야 하며, 슬러그는
> `key`로 살아남는다. 빌드하고 유지하는 동안의 `legacy placement slug → new {id, key}` 룩업
> 마이그레이션。

### 예제##example

**레거시 — ad-unit 아래 배치 생성:**

```json
POST /monetize/v1/projects/{projectId}/stores/apple/adunits/{adUnitId}/placements
[ { "name": "Rewarded Placement", "id": "rewarded_01",
    "addTargets": [ { "value": 18.5, "type": "global" } ] } ]
```

**새로운 — 플레이스먼트당 한 번 생성(타겟 삭제, adFormat + 구성 추가):**

```json
POST /ads/publisher/public/v1/organizations/{orgId}/applications/{applicationId}/placements
{
  "name": "보상형 광고 배치",
  "adFormat": "rewarded",
  "adFormatConfigurations": {
    "name": "coins",
    "value": 100,
    "adminSettings": { "allowSkip": true, "allowSkipInSeconds": 5, "muteVideo": false }
  }
}
```

## 차단 D — 조직 배치 목록##block-d--organization-placements-list

### 변경한 사항##what-changed

동일한 의도(조직 내 모든 플레이스먼트 나열) 하지만 다른 응답 형태: eCPM `targets`
사라지고, `adFormat`, `platform`, `storeId`가 추가되고, 레거시 `id`가 `placementId`가 됩니다.
(사람이 읽을 수 있는 키입니다.) **활성 iOS/Android** 플레이스먼트만 반환됩니다.

### 엔드포인트 매핑##endpoint-mapping

| **레거시**                                             | **신규**                                            |
| --------------------------------------------------- | ------------------------------------------------- |
| `GET /monetize/v1/organizations/{orgId}/placements` | `GET /public/v1/organizations/{orgId}/placements` |

### 필드 매핑(`placementWithGameId` → `OrganizationPlacementPublicResponseDto`)##field-mapping-(placementwithgameid--organizationplacementpublicresponsedto)

| **레거시 필드**      | **새 필드**                                                 | **참고**                                |
| --------------- | -------------------------------------------------------- | ------------------------------------- |
| `id`            | `placementId`                                            | 현재 인간이 읽을 수 있는 키(예: `Banner_Android`) |
| `name`          | `name`                                                   | 변경되지 않은                               |
| `placementType` | `placementType`                                          | 변경되지 않음(문자열, 예: `bidding`)            |
| `gameId`        | `gameId` (정수, nullable)                                  | 변경되지 않은                               |
| `targets`(배열)   | —                                                        | 거의 제거된                                |
| —               | `adFormat` (`rewarded`/`interstitial`/`banner`/`native`) | new                                   |
| —               | `platform` (`ios`/`android`/`""`)                        | 신규, iOS 또는 Android가 없는 경우 `""`        |
| —               | `storeId`(nullable)                                      | new                                   |

## 차단 E - eCPM 타겟 제거##block-e---ecpm-targets-removed

### ± 새로운 공용 API 해당하는 항목이 없습니다.##-no-equivalent-in-the-new-public-api

모든 레거시 타겟 엔드포인트가 사라지고 이 API에서는 **교체할 수 없습니다**.
`getTargets`, `createTargets`, `getTargetDetail`, `patchTarget`, `deleteTarget`
플레이스먼트/광고 유닛의 `targets`/`addTargets` 필드

**에이전트 지침:**

1. 이를 새 엔드포인트에 매핑 **하지 마십시오**. `…/targets` 경로를 생성하지 마십시오.
2. 모든 플레이스먼트 생성/업데이트 페이로드에서 `targets`/`addTargets`를 제거합니다.
3. 이 API를 통해 클라이언트의 통합이 eCPM 기본 가격을 관리하는 경우 이 값을 **중지하고
   인간의** 차단점 — eCPM 대상 관리가 새로운 퍼블리셔에 포함되지 않음
   공용 API 및 대역에서 처리해야 합니다(예: 다른 Unity 표면 또는 제품을 통해 처리)
   decision). 명시적으로 플래그를 플래그. 가격 로직을 표시하지 않고는 자동으로 내보내지 마십시오.

## 차단 F — 애플리케이션 테스트 모드(신규)##block-f--application-test-mode-(new)

레거시 동등은 없습니다. 새로운 엔드포인트:

* `GET …/applications/{applicationId}/test-mode` → `{ id, testMode }`
* `PATCH …/applications/{applicationId}/test-mode` 와 `{ "testMode": "forceAll" | "forceOff" }`

애플리케이션 응답(차단 A)에도 `testMode`가 존재합니다. 마이그레이션은 추가 단계일 뿐이며,
레거시 API 포팅 항목이 없습니다. 클라이언트 테스트 모드 컨트롤을 원하는 경우 이를 노출해야 합니다.

## 차단 G — 테스트 장치(새로운 장치)##block-g--test-devices-(new)

레거시 동등은 없습니다. Org 범위 CRUD:

* `POST …/organizations/{orgId}/test-devices` — `CreateTestDevicePublicDto` `{ name*, advertisingId*, platform? }`
* `GET …/organizations/{orgId}/test-devices` — 목록(옵션 `platform` 필터)
* `GET …/organizations/{orgId}/test-devices/{testDeviceId}` — get one
* `PATCH …/organizations/{orgId}/test-devices/{testDeviceId}` — `UpdateTestDevicePublicDto`
* `DELETE …/organizations/{orgId}/test-devices/{testDeviceId}` — 204

`testDeviceId`는 UUID입니다. `platform`는 전체 플랫폼 열거형을 사용합니다.
(`Android`, `iOS`, `OSX`, `Windows`, `Linux`, `WebGL`, `Windows_Store`, `PS4`, `PS5`, `XboxOne`,
`tvOS`, `Switch`, `VisionOS`). 마이그레이션은 추가 단계입니다.

## 권장 마이그레이션 알고리즘(에이전트 결정적 순서)##suggested-migration-algorithm-(deterministic-order-for-the-agent)

1. **설정:** 지지대 호스트를 `…/ads/publisher`와 경로 접두사 `/monetize/v1` → `/public/v1`로 변경합니다.
   기본 인증을 유지합니다. 모든 `dryrun` 파라미터를 제거합니다.
2. **애플리케이션(차단 A):** 각 레거시 프로젝트에 대해 스토어당 하나의 애플리케이션을 생성합니다. 변환하다(동사), 트랜스폼(명사)
   `coppa` 열거형→부울, 드롭 `adsProvider`, 설정 `platform`/`store`/`storeId`. 을 통한 그룹화
   `projectName`/`projectId`. `legacy projectId+store → new {applicationId, projectId}` 맵을 유지합니다.
3. **플레이스먼트(차단 C):** 각 레거시 배치에 대해 맵에서 소유 애플리케이션을 결정합니다.
   `adFormat`(레거시 ad-unit에서) + `adFormatConfigurations`로 개별적으로 생성
   (ad-unit/admin 설정에서) **드롭 `targets`/`addTargets`.** 지속
   `legacy placement slug → new {id, key}`.
4. **포인트 변경 식별자:** 저장된 레거시 배치 슬러그를 경로에 대한 새 UUID `id`로 대체
   ops(표시할 `key` 유지). 저장된 `projectId` 기반 앱 리프를 `applicationId`로 교체합니다.
5. **아카이브 시맨틱:** `archive:true/false` PATCH 호출을 `DELETE` / `PATCH …/restore`로 대체한다.
6. **리스트/읽기 모양:** 필드 테이블별로 응답 파싱 업데이트(esp. org list - 차단 D).
7. **eCPM 타겟(차단 E):** 타겟 코드를 제거하고, **인간에게 공백을 보고**한다.
8. **선택적인 새로운 기능:** 요청 시에만 테스트 모드(F) 및 테스트 장치(G)를 연결합니다.
9. **속도 제한:** 캡은 ≤1/s 및 ≤60/hour에서 생성되고, 값은 ≤20/s입니다.

## 확인 체크리스트(마이그레이션 후 실행)##validation-checklist-(run-after-migration)

* [ ] 요청 경로에는 `/monetize/v1`, `/projects/{`, `/adunits`, `/stores/` 또는 `/targets`가 포함되어 있지 않습니다.
* [ ] 요청 바디에는 `adsProvider`, `addTargets`, `targets` 또는 `archive`가 포함되어 있지 않습니다.
* [ ] `coppa`는 모든 곳에서 부울(아무 `"compliant"`/`"non_compliant"` 없음).
* [ ] 모든 플레이스먼트 생성/업데이트는 유효한 `adFormat` 및 `adFormatConfigurations`를 전송합니다.
  형식과 일치합니다.
* [ ] 플레이스먼트 경로 파라미터는 레거시 슬러그(`key`)가 아니라 새 UUID `id`를 사용합니다.
* [ ] 두 개의 상점이 있는 레거시 프로젝트는 하나의 `projectId`를 공유하는 두 개의 애플리케이션을 생성했습니다.
* [ ] eCPM 타겟 관리는 클라이언트 지원하지 않는 공백으로 나타났습니다.

## 빠른 레퍼런스: 제거됨/동등하지 않음(마이그레이션 또는 제조하지 않음)##quick-reference:--removed-/-no-equivalent-(do-not-migrate-or-fabricate)

* 모든 **Ad-Unit** 엔드포인트와 ad-unit 리소스
* 모든 **eCPM 타겟** 엔드포인트 및 `targets`/`addTargets` 필드
* 프로젝트 `adsProvider` 및 `maxCaps`.
* `dryrun` 및 `showarchived`쿼리 파라미터
* 클라이언트가 선택한 플레이스먼트 ID(서버가 UUID를 할당하고 슬러그를 `key`로 유지).
