
# Registra una passkey

`POST /fapi/v1/me/passkeys` · chiamato dall'host di accesso dell'ambiente

Completa una registrazione avviata con [`POST /fapi/v1/me/passkeys/registration-options`](./registration-options): verifica la risposta del browser, memorizza la passkey o la chiave di sicurezza e la restituisce. L'utente riceve un'email di sicurezza sul nuovo metodo di accesso; questa email non può essere disattivata. Richiede il cookie `__Host-idp_session` dell'utente autenticato; se il cookie manca, è scaduto o è stato revocato, restituisce `401 unauthenticated`. Un'app di prima parte su un'altra origine può invece inviare il proprio token di accesso alla Front API (`Authorization: DPoP <token>` con una prova DPoP; vedi [Token di accesso della Front API](/docs/api/fapi/front-api-token)).

Le regole sulla sessione sono quelle della chiamata di avvio: verificata negli ultimi 10 minuti e aperta con un secondo fattore se l'utente ne ha uno.

## Richiesta

Intestazioni:

| Intestazione | Obbligatoria | Descrizione |
| --- | --- | --- |
| `Idp-Client` | sì | Identificatore dell'SDK. |
| `Origin` | sì | Deve essere esattamente `https://<host di accesso>`. |
| `Content-Type` | sì | `application/json`. |

Corpo:

| Campo | Tipo | Obbligatorio | Descrizione |
| --- | --- | --- | --- |
| `ceremony_id` | string | sì | Il `ceremony_id` della chiamata di avvio. |
| `credential` | object | sì | Il risultato di `navigator.credentials.create` come JSON (`PublicKeyCredential.toJSON()`), al massimo 16 KiB. |

## Risposta

`201 Created`, il nuovo autenticatore:

```json
{
  "id": "cred_01j9zzzzzzzzzzzzzzzzzzzzzz",
  "type": "passkey",
  "label": "iCloud Keychain",
  "status": "active",
  "synced": true,
  "created_at": "2026-10-12T09:00:00Z",
  "last_used_at": null
}
```

## Errori

Una registrazione rifiutata non viene conteggiata come tentativo fallito; ricomincia con una nuova registrazione.

| Stato | Codice | Quando |
| --- | --- | --- |
| 400 | `malformed_request` | Il corpo non è JSON valido. |
| 400 | `unknown_field` | Il corpo contiene un campo diverso da `ceremony_id` e `credential`. |
| 401 | `unauthenticated` | Nessun cookie di sessione valido. |
| 401 | `step_up_required` | La sessione è troppo vecchia oppure serve un secondo fattore; verifica di nuovo con i valori nell'intestazione `WWW-Authenticate`. |
| 403 | `origin_not_allowed` | `Origin` manca o è errato. |
| 409 | `credential_already_registered` | Questo autenticatore è già registrato nell'ambiente. |
| 409 | `credential_limit_reached` | L'utente ha già il numero massimo di passkey e chiavi di sicurezza. |
| 413 | `payload_too_large` | Il corpo è troppo grande. |
| 415 | `unsupported_media_type` | `Content-Type` non è `application/json`. |
| 422 | `validation_failed` | `/ceremony_id`: la registrazione è sconosciuta, scaduta, già usata o appartiene a un'altra sessione. `/credential`: la risposta manca o non viene verificata. |
| 422 | `device_bound_passkey_required` | L'ambiente accetta solo passkey che restano su un unico dispositivo. |
| 422 | `aaguid_not_allowed` | L'ambiente non consente questo modello di autenticatore. |
| 422 | `attestation_untrusted` | L'ambiente richiede un'attestazione attendibile e questa non lo è. |

## Esempio

```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": "…"}}}'
```

## Dati personali

Questo endpoint tratta dati personali: credenziali. [Dati personali nelle API di KeenAuth](/docs/policies/personal-data) descrive cosa viene memorizzato, per quanto tempo viene conservato e come viene cancellato. Non inviare categorie particolari di dati personali (salute, dati biometrici, religione, opinioni politiche, orientamento sessuale) in nessun campo.
