# Verify a passkey registration and enroll the credential

`POST /v1/auth/mfa/passkey/registration/verify`

Verifies the browser's registration against the challenge issued to this
partial-auth session and stores the credential, active at once. For the
account's first second factor the single-use recovery codes are returned
exactly ONCE and the flow parks at the recovery-codes step (recovery-ack);
for a further factor the session is elevated and `redirectTo` resumes the
flow. A registration that does not verify is answered uniformly (401).

## Authorization

None: this endpoint does not take API credentials.

This endpoint reads the browser session from the `Cookie` header: The request Cookie header; the interactive login / pending-auth session cookie, and the browser marker a sign-in is recognized by, are read from it.

This endpoint is rate limited; it answers `429` when a limit is hit.

## Headers

- `Cookie` (string, optional): The request Cookie header; the interactive login / pending-auth session cookie, and the browser marker a sign-in is recognized by, are read from it.
- `Origin` (string, optional): Browser Origin, checked (with Sec-Fetch-Site) as a same-origin/CSRF guard.
- `Sec-Fetch-Site` (string, optional): Fetch metadata; only same-origin is trusted (CSRF guard).
- `Content-Type` (string, optional): Must be application/json — a JSON body cannot be a CORS simple request, closing form-based CSRF.
- `User-Agent` (string, optional)
- `Accept-Language` (string, optional): The browser's preferred languages (RFC 9110 §12.5.4); a message the operation queues, such as a new-sign-in alert, is written in the first one the server ships.

## Request body (`application/json`)

The browser's answer to a registration ceremony.

- `credential` (object, required): What PublicKeyCredential.toJSON produces for a created credential.
  - `id` (string, required): Bytes, base64url-encoded without padding. Constraints: at most 8192 characters; pattern `^[A-Za-z0-9_-]*$`.
  - `type` (enum, required): One of: `"public-key"`.
  - `response` (object, required): The authenticator's attestation response (§5.2.1).
    - `clientDataJSON` (string, required): Bytes, base64url-encoded without padding. Constraints: at most 8192 characters; pattern `^[A-Za-z0-9_-]*$`.
    - `attestationObject` (string, required): Bytes, base64url-encoded without padding. Constraints: at most 8192 characters; pattern `^[A-Za-z0-9_-]*$`.
    - `transports` (array of strings, optional): Constraints: at most 8 items; each item: at most 32 characters.
    - `authenticatorData` (string, optional): Bytes, base64url-encoded without padding. Constraints: at most 8192 characters; pattern `^[A-Za-z0-9_-]*$`.
    - `publicKey` (string, optional): Bytes, base64url-encoded without padding. Constraints: at most 8192 characters; pattern `^[A-Za-z0-9_-]*$`.
    - `publicKeyAlgorithm` (integer (int64), optional)
  - `rawId` (string, optional): Bytes, base64url-encoded without padding. Constraints: at most 8192 characters; pattern `^[A-Za-z0-9_-]*$`.
  - `authenticatorAttachment` (enum, optional): One of: `"platform"`, `"cross-platform"`.
  - `clientExtensionResults` (object, optional)
- `label` (string, optional): The user's name for the credential. Constraints: at most 64 characters.

No other properties are accepted.

## Responses

### 200 OK

The credential was enrolled.

Content type: `application/json`

Headers:
- `Set-Cookie` (array of strings)
- `Cache-Control` = `no-store`

A verified registration. When the credential is the account's first second factor the recovery codes are returned once and the flow parks at the recovery-codes step; otherwise the session is elevated and `redirectTo` resumes the flow.

- `codes` (array of strings, optional): The plaintext recovery codes (store securely; not retrievable again).
- `redirectTo` (string, optional): Where to resume the flow once the session is elevated.

### 429 Too Many Requests

Too many requests.

Content type: `application/problem+json`

RFC 9457 (formerly RFC 7807) problem details (application/problem+json). `code` is a stable machine-readable slug for the problem kind (it mirrors the last path segment of `type`), so consumers can branch on it without parsing prose. `type` is `https://nauthera.io/problems/<code>`, which resolves to the code's runbook: why it happened, who is affected and what to do (RFC 9457 §3.1.1). A problem without a code has `about:blank`. `errors` is present on validation failures and lists every offending field.

- `title` (string, required): Example: `"Request validation failed"`.
- `status` (integer, required): Example: `400`.
- `type` (string (uri), optional): Example: `"https://nauthera.io/problems/validation_error"`.
- `detail` (string, optional): Example: `"One or more fields did not satisfy the API schema."`.
- `code` (enum, optional): The kind of problem, as a stable machine-readable slug a client branches on and translates rather than parsing `title` or `detail`, which are English prose for logs. A problem whose status says everything the server knows carries no `code`. One of: `"invalid_request"`, `"invalid_credentials"`, `"forbidden"`, `"unauthorized"`, `"insufficient_user_authentication"`, `"protected_account"`, `"password_held_by_directory"`, `"last_role_administrator"`, `"invalid_code"`, `"too_many_requests"`, `"too_many_attempts"`, `"account_locked"`, `"account_unavailable"`, `"already_enrolled"`, `"registration_disabled"`, `"registration_conflict"`, `"captcha_failed"`, `"invalid_reset_link"`, `"password_reused"`, `"password_too_short"`, `"password_too_long"`, `"password_compromised"`, `"invalid_verification_link"`, `"invalid_invitation_link"`, `"mail_relay_missing"`, `"email_unverified"`, `"not_found"`, `"conflict"`, `"unavailable"`, `"busy"`, `"window_too_large"`, `"too_many_series"`, `"internal_error"`, `"rate_limited"`, `"malformed"`, `"body_too_large"`, `"required"`, `"validation_error"`, `"unknown_org"`, `"invalid_token"`, `"invalid_dpop_proof"`, `"use_dpop_nonce"`, `"server_error"`.

  `validation_error` is the one code whose message is not a sentence about
  the code itself: the request failed the schema, and the specifics are
  the `errors` list — one entry per offending field — which a form renders
  on those fields. `required` and `malformed` are spelled like two of the
  per-field violation codes (`FieldError.code`) but mean something else;
  the two vocabularies are told apart by position, never by spelling.

  **Compatibility:** this is a CLOSED enum, with the same cost as
  `AuditAction`: a client generated against an older version of this
  document rejects a problem carrying a code added after it was built.
  Regenerate on upgrade, or decode the field as a plain string. It is
  enumerated anyway so that what a client must translate is knowable
  from the contract alone.
- `instance` (string, optional): Example: `"/introspect"`.
- `request_id` (string, optional): Example: `"8d94ed49-1caf-4ab3-9594-557142f2d023"`.
- `trace_id` (string, optional): Distributed-trace id (hex) for correlating this response with traces and logs. Present only when tracing is enabled and the request was recorded. Opaque and safe to log/share. Example: `"4bf92f3577b34da6a3ce929d0e0e4736"`.
- `errors` (array of objects, optional): Field-level violations (present for validation errors).

  Each item: A single field-level validation violation.
  - `field` (string, required): Offending field, dotted for nested bodies (e.g. child.id). Example: `"token_type_hint"`.
  - `code` (string, required): Machine-readable violation code: required, too_short, too_long, pattern, invalid_format, not_allowed, minimum, maximum, multiple_of, too_few, too_many, not_unique, malformed. Example: `"not_allowed"`.
  - `message` (string, required): Example: `"field token_type_hint must be one of [access_token, refresh_token]"`.

### Error

Problem details (RFC 9457).

Content type: `application/problem+json`

RFC 9457 (formerly RFC 7807) problem details (application/problem+json). `code` is a stable machine-readable slug for the problem kind (it mirrors the last path segment of `type`), so consumers can branch on it without parsing prose. `type` is `https://nauthera.io/problems/<code>`, which resolves to the code's runbook: why it happened, who is affected and what to do (RFC 9457 §3.1.1). A problem without a code has `about:blank`. `errors` is present on validation failures and lists every offending field.

- `title` (string, required): Example: `"Request validation failed"`.
- `status` (integer, required): Example: `400`.
- `type` (string (uri), optional): Example: `"https://nauthera.io/problems/validation_error"`.
- `detail` (string, optional): Example: `"One or more fields did not satisfy the API schema."`.
- `code` (enum, optional): The kind of problem, as a stable machine-readable slug a client branches on and translates rather than parsing `title` or `detail`, which are English prose for logs. A problem whose status says everything the server knows carries no `code`. One of: `"invalid_request"`, `"invalid_credentials"`, `"forbidden"`, `"unauthorized"`, `"insufficient_user_authentication"`, `"protected_account"`, `"password_held_by_directory"`, `"last_role_administrator"`, `"invalid_code"`, `"too_many_requests"`, `"too_many_attempts"`, `"account_locked"`, `"account_unavailable"`, `"already_enrolled"`, `"registration_disabled"`, `"registration_conflict"`, `"captcha_failed"`, `"invalid_reset_link"`, `"password_reused"`, `"password_too_short"`, `"password_too_long"`, `"password_compromised"`, `"invalid_verification_link"`, `"invalid_invitation_link"`, `"mail_relay_missing"`, `"email_unverified"`, `"not_found"`, `"conflict"`, `"unavailable"`, `"busy"`, `"window_too_large"`, `"too_many_series"`, `"internal_error"`, `"rate_limited"`, `"malformed"`, `"body_too_large"`, `"required"`, `"validation_error"`, `"unknown_org"`, `"invalid_token"`, `"invalid_dpop_proof"`, `"use_dpop_nonce"`, `"server_error"`.

  `validation_error` is the one code whose message is not a sentence about
  the code itself: the request failed the schema, and the specifics are
  the `errors` list — one entry per offending field — which a form renders
  on those fields. `required` and `malformed` are spelled like two of the
  per-field violation codes (`FieldError.code`) but mean something else;
  the two vocabularies are told apart by position, never by spelling.

  **Compatibility:** this is a CLOSED enum, with the same cost as
  `AuditAction`: a client generated against an older version of this
  document rejects a problem carrying a code added after it was built.
  Regenerate on upgrade, or decode the field as a plain string. It is
  enumerated anyway so that what a client must translate is knowable
  from the contract alone.
- `instance` (string, optional): Example: `"/introspect"`.
- `request_id` (string, optional): Example: `"8d94ed49-1caf-4ab3-9594-557142f2d023"`.
- `trace_id` (string, optional): Distributed-trace id (hex) for correlating this response with traces and logs. Present only when tracing is enabled and the request was recorded. Opaque and safe to log/share. Example: `"4bf92f3577b34da6a3ce929d0e0e4736"`.
- `errors` (array of objects, optional): Field-level violations (present for validation errors).

  Each item: A single field-level validation violation.
  - `field` (string, required): Offending field, dotted for nested bodies (e.g. child.id). Example: `"token_type_hint"`.
  - `code` (string, required): Machine-readable violation code: required, too_short, too_long, pattern, invalid_format, not_allowed, minimum, maximum, multiple_of, too_few, too_many, not_unique, malformed. Example: `"not_allowed"`.
  - `message` (string, required): Example: `"field token_type_hint must be one of [access_token, refresh_token]"`.

## Example request

```bash
curl -X POST 'https://auth.example.com/v1/auth/mfa/passkey/registration/verify' \
  -H "Content-Type: application/json" \
  -d '{
    "credential": {
      "id": "string",
      "rawId": "string",
      "type": "public-key",
      "authenticatorAttachment": "platform",
      "clientExtensionResults": {},
      "response": {
        "clientDataJSON": "string",
        "attestationObject": "string",
        "transports": [
          "string"
        ],
        "authenticatorData": "string",
        "publicKey": "string",
        "publicKeyAlgorithm": 0
      }
    }
  }'
```

## Example response (200)

```json
{}
```
