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"}'