Change the passkey rpId of a connection
POST /v1/connections/{connection_id}:change-rp-id · permission connections:write
Moves a passkey connection to a new rpId and starts a migration window. A passkey is bound to the rpId it was registered with, so once users have passkeys this is the only way to change the rpId: a PATCH that changes settings.rp_id is rejected with 409 rp_id_change_requires_migration.
During the window, users can still sign in with passkeys of the previous rpId, and new passkeys are registered on the new rpId. When the window ends, or when you end it early with Complete the rpId migration, the passkeys that are still on the previous rpId are revoked (reason rp_id_changed). Users who have no other sign-in method then need passwordless email sign-in or account recovery. Only one migration can run at a time in an environment.
The connection's rpId, the migration record, the management audit entry and the passkey_rp_id.migration_started event are written together. A custom domain that is the rpId, or the previous rpId of a running migration, cannot be deleted (409 domain_in_use_by_rp_id).
Request
| Field | Type | Required | Description |
|---|---|---|---|
connection_id |
path | yes | Passkey connection ID (conn_…). |
If-Match |
header | yes | The connection's ETag (see Get a connection). |
Idempotency-Key |
header | no | Optional key that makes a retried request safe (16-64 characters). |
new_rp_id |
string | yes | The new rpId: the host name of an active custom domain of the environment (lower-case, IDNA A-label), or an empty string to use each sign-in host as its own rpId. At most 253 bytes. |
confirm_rp_id |
string | yes | new_rp_id typed again. |
migration_window_days |
integer | no | How many days passkeys of the previous rpId keep working: 30-365, default 90. |
{
"new_rp_id": "login.example.com",
"confirm_rp_id": "login.example.com",
"migration_window_days": 90
}
Response
200 OK; the migration and an ETag header with the connection's new version.
{
"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
}
| Field | Description |
|---|---|
previous_rp_id, new_rp_id |
The rpId before and after the change; null means each sign-in host is its own rpId. |
status |
active while the window runs; completed when it ended; completed_early when it was ended early. |
ends_at |
When the window is scheduled to end. |
completed_at, revoked_count, new_registration_count |
Written when the migration closes; null while it is active. |
fallback_enabled_by_migration |
true when a fallback sign-in method was turned on for the window because passkeys were the only first factor. |
Errors
| HTTP | code | When |
|---|---|---|
| 400 | unknown_field |
The body contains an unknown field. |
| 404 | not_found |
The connection does not exist or belongs to another environment. |
| 409 | rp_id_migration_in_progress |
Another rpId migration is running in this environment. |
| 412 | precondition_failed |
If-Match does not match the current version. |
| 422 | validation_failed |
A field is missing, too long or out of range; new_rp_id is not an active custom domain of the environment or equals the current rpId; confirm_rp_id differs; or the connection is not a passkey connection (errors[].pointer: /new_rp_id, /confirm_rp_id, /migration_window_days, /connection_id). |
| 428 | precondition_required |
The If-Match header is missing. |
| 401 | unauthenticated |
The credential is missing, invalid or revoked. |
| 403 | insufficient_permissions |
The credential lacks the required permission. |
| 429 | rate_limited |
The rate limit was exceeded; retry after the Retry-After period. |
| 503 | service_unavailable |
A dependency is temporarily unavailable; retry after the Retry-After period. |
Example
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"}'
keenauth connections change-rp-id conn_01j9xp6q0r1s5t7v9w1x3y5z7b --if-match '"3"' --yes \
--data '{"new_rp_id":"login.example.com","confirm_rp_id":"login.example.com"}'