Registrar una clave de acceso
POST /fapi/v1/me/passkeys · llamado desde el host de inicio de sesión del entorno
Finaliza un registro iniciado con POST /fapi/v1/me/passkeys/registration-options: verifica la respuesta del navegador, almacena la clave de acceso o la clave de seguridad y la devuelve. El usuario recibe un correo de seguridad sobre el nuevo método de inicio de sesión; este correo no puede desactivarse. Requiere la cookie __Host-idp_session del usuario autenticado; si la cookie falta, ha caducado o ha sido revocada, devuelve 401 unauthenticated. Una aplicación de primera parte en otro origen puede enviar en su lugar su token de acceso a la API frontal (Authorization: DPoP <token> con una prueba DPoP; consulta Token de acceso a la API frontal).
Las reglas de sesión son las de la llamada de inicio: verificada en los últimos 10 minutos y abierta con un segundo factor si el usuario tiene uno.
Solicitud
Encabezados:
| Encabezado | Obligatorio | Descripción |
|---|---|---|
Idp-Client |
sí | Identificador del SDK. |
Origin |
sí | Debe ser exactamente https://<host de inicio de sesión>. |
Content-Type |
sí | application/json. |
Cuerpo:
| Campo | Tipo | Obligatorio | Descripción |
|---|---|---|---|
ceremony_id |
string | sí | El ceremony_id de la llamada de inicio. |
credential |
object | sí | El resultado de navigator.credentials.create como JSON (PublicKeyCredential.toJSON()), como máximo 16 KiB. |
Respuesta
201 Created, el nuevo autenticador:
{
"id": "cred_01j9zzzzzzzzzzzzzzzzzzzzzz",
"type": "passkey",
"label": "iCloud Keychain",
"status": "active",
"synced": true,
"created_at": "2026-10-12T09:00:00Z",
"last_used_at": null
}
Errores
Un registro rechazado no cuenta como intento fallido; vuelve a empezar con un nuevo registro.
| Estado | Código | Cuándo |
|---|---|---|
| 400 | malformed_request |
El cuerpo no es JSON válido. |
| 400 | unknown_field |
El cuerpo tiene un campo distinto de ceremony_id y credential. |
| 401 | unauthenticated |
No hay una cookie de sesión válida. |
| 401 | step_up_required |
La sesión es demasiado antigua o se requiere un segundo factor; vuelve a verificarte con los valores del encabezado WWW-Authenticate. |
| 403 | origin_not_allowed |
Origin falta o es incorrecto. |
| 409 | credential_already_registered |
Este autenticador ya está registrado en el entorno. |
| 409 | credential_limit_reached |
El usuario ya tiene el número máximo de claves de acceso y claves de seguridad. |
| 413 | payload_too_large |
El cuerpo es demasiado grande. |
| 415 | unsupported_media_type |
Content-Type no es application/json. |
| 422 | validation_failed |
/ceremony_id: el registro es desconocido, ha caducado, ya se usó o pertenece a otra sesión. /credential: la respuesta falta o no se verifica. |
| 422 | device_bound_passkey_required |
El entorno solo acepta claves de acceso que permanecen en un único dispositivo. |
| 422 | aaguid_not_allowed |
El entorno no permite este modelo de autenticador. |
| 422 | attestation_untrusted |
El entorno requiere una atestación de confianza y esta no lo es. |
Ejemplo
curl -X POST https://example.keenauth.app/fapi/v1/me/passkeys \
-H "Idp-Client: my-app/1.0.0 (node/22.0.0)" \
-H "Origin: https://example.keenauth.app" \
-H "Content-Type: application/json" \
-b cookies.txt \
-d '{"ceremony_id": "CQAAAAAAAAAAAAAAAAAAAA", "credential": {"id": "…", "rawId": "…", "type": "public-key", "response": {"clientDataJSON": "…", "attestationObject": "…"}}}'
Datos personales
Este endpoint contiene datos personales: credenciales. Datos personales en las API de KeenAuth describe qué se almacena, durante cuánto tiempo se conserva y cómo se elimina. No envíes categorías especiales de datos personales (salud, datos biométricos, religión, opiniones políticas, orientación sexual) en ningún campo.