
# 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`](./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](/docs/api/fapi/front-api-token)).

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:

```json
{
  "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

```bash
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](/docs/policies/personal-data) 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.
