
# 연결의 패스키 rpId 변경

`POST /v1/connections/{connection_id}:change-rp-id` · 권한 `connections:write`

패스키 연결을 새 rpId로 이동하고 마이그레이션 기간을 시작합니다. 패스키는 등록된 rpId에 묶여 있으므로, 사용자가 패스키를 보유한 후에는 이 방법으로만 rpId를 변경할 수 있습니다. `settings.rp_id`를 변경하는 `PATCH` 요청은 `409 rp_id_change_requires_migration`으로 거부됩니다.

마이그레이션 기간 동안 사용자는 이전 rpId의 패스키로 여전히 로그인할 수 있으며, 새 패스키는 새 rpId에 등록됩니다. 기간이 끝나거나 [rpId 마이그레이션 완료](complete-rp-id-migration.md)로 조기에 종료하면, 이전 rpId에 남아 있는 패스키는 폐기됩니다(사유 `rp_id_changed`). 다른 로그인 방법이 없는 사용자는 비밀번호 없는 이메일 로그인 또는 계정 복구가 필요합니다. 한 환경에서는 한 번에 하나의 마이그레이션만 실행할 수 있습니다.

연결의 rpId, 마이그레이션 기록, 관리 감사 항목, `passkey_rp_id.migration_started` 이벤트는 함께 기록됩니다. rpId이거나 실행 중인 마이그레이션의 이전 rpId인 사용자 지정 도메인은 삭제할 수 없습니다(`409 domain_in_use_by_rp_id`).

## 요청

| 필드 | 유형 | 필수 | 설명 |
|---|---|---|---|
| `connection_id` | path | 예 | 패스키 연결 ID(`conn_…`). |
| `If-Match` | header | 예 | 연결의 `ETag`([연결 조회](get.md) 참조). |
| `Idempotency-Key` | header | 아니요 | 재시도 요청을 안전하게 만드는 선택 키(16~64자). |
| `new_rp_id` | string | 예 | 새 rpId: 환경의 `active` 상태 사용자 지정 도메인의 호스트 이름(소문자, IDNA A-label), 또는 각 로그인 호스트를 자체 rpId로 사용하려면 빈 문자열. 최대 253바이트. |
| `confirm_rp_id` | string | 예 | `new_rp_id`를 다시 입력한 값. |
| `migration_window_days` | integer | 아니요 | 이전 rpId의 패스키가 계속 동작하는 일수: 30~365, 기본값 90. |

```json
{
  "new_rp_id": "login.example.com",
  "confirm_rp_id": "login.example.com",
  "migration_window_days": 90
}
```

## 응답

`200 OK`; 마이그레이션 정보와 연결의 새 버전을 담은 `ETag` 헤더.

```json
{
  "id": "rpm_01j9xr4a6b8c0d2e4f6g8h0j2k",
  "connection_id": "conn_01j9xp6q0r1s5t7v9w1x3y5z7b",
  "previous_rp_id": null,
  "new_rp_id": "login.example.com",
  "status": "active",
  "started_at": "2026-10-27T10:00:00Z",
  "ends_at": "2027-01-25T10:00:00Z",
  "completed_at": null,
  "revoked_count": null,
  "new_registration_count": null,
  "fallback_enabled_by_migration": false
}
```

| 필드 | 설명 |
|---|---|
| `previous_rp_id`, `new_rp_id` | 변경 전후의 rpId; `null`은 각 로그인 호스트가 자체 rpId임을 의미합니다. |
| `status` | 기간이 진행 중이면 `active`; 기간이 끝났으면 `completed`; 조기에 종료되었으면 `completed_early`. |
| `ends_at` | 기간이 종료될 예정 시각. |
| `completed_at`, `revoked_count`, `new_registration_count` | 마이그레이션이 종료될 때 기록되며, `active` 상태에서는 `null`. |
| `fallback_enabled_by_migration` | 패스키가 유일한 1차 인증 수단이어서 기간 동안 대체 로그인 방법이 켜진 경우 `true`. |

## 오류

| HTTP | code | 발생 조건 |
|---|---|---|
| 400 | `unknown_field` | 요청 본문에 알 수 없는 필드가 있습니다. |
| 404 | `not_found` | 연결이 존재하지 않거나 다른 환경에 속합니다. |
| 409 | `rp_id_migration_in_progress` | 이 환경에서 다른 rpId 마이그레이션이 실행 중입니다. |
| 412 | `precondition_failed` | `If-Match`가 현재 버전과 일치하지 않습니다. |
| 422 | `validation_failed` | 필드가 없거나, 너무 길거나, 범위를 벗어납니다. `new_rp_id`가 환경의 `active` 상태 사용자 지정 도메인이 아니거나 현재 rpId와 같습니다. `confirm_rp_id`가 다릅니다. 또는 연결이 패스키 연결이 아닙니다(`errors[].pointer`: `/new_rp_id`, `/confirm_rp_id`, `/migration_window_days`, `/connection_id`). |
| 428 | `precondition_required` | `If-Match` 헤더가 없습니다. |
| 401 | `unauthenticated` | 자격 증명이 없거나, 유효하지 않거나, 폐기되었습니다. |
| 403 | `insufficient_permissions` | 자격 증명에 필요한 권한이 없습니다. |
| 429 | `rate_limited` | 요청 한도를 초과했습니다. `Retry-After` 기간 후에 다시 시도하십시오. |
| 503 | `service_unavailable` | 종속 서비스를 일시적으로 사용할 수 없습니다. `Retry-After` 기간 후에 다시 시도하십시오. |

## 예시

```bash
curl -X POST "https://api.eu.keenauth.com/v1/connections/conn_01j9xp6q0r1s5t7v9w1x3y5z7b:change-rp-id" \
  -H "Authorization: Bearer $KEENAUTH_SECRET_KEY" \
  -H 'If-Match: "3"' \
  -H "Content-Type: application/json" \
  -d '{"new_rp_id":"login.example.com","confirm_rp_id":"login.example.com"}'
```

```bash
keenauth connections change-rp-id conn_01j9xp6q0r1s5t7v9w1x3y5z7b --if-match '"3"' --yes \
  --data '{"new_rp_id":"login.example.com","confirm_rp_id":"login.example.com"}'
```
