:::endpoint POST /v1/auth/token/two-factor/passkey-options

A passkey answer has to be signed over a challenge this API issued, so ask for
one before showing the system sheet. Send the `challenge` from
[the first step](/docs/api/apps/sign-in) and nothing else: who is signing in and
which passkeys they have are read from it.

Only passkeys registered [through an app](/docs/api/apps/passkeys) are listed. A
passkey somebody made on their firm's website is bound to that domain, and an
app can neither see it nor ask for it.

## Request

```bash
curl https://api.proppertrading.com/v1/auth/token/two-factor/passkey-options \
  -H "Content-Type: application/json" \
  -d '{
    "challenge": "a14ea608e89261062f4e90792701ef2a7c5ad1188c8537108efd788d1390ce03"
  }'
```

| Field       | Type   | Description                         |
| ----------- | ------ | ----------------------------------- |
| `challenge` | string | From the sign-in response. Required |

## Response

The `publicKey` half of a WebAuthn request, ready to hand to the platform. Field
names are WebAuthn's, not ours.

```json
{
    "data": {
        "object": "passkey_request_options",
        "challenge": "3q2-7_8",
        "timeout": 60000,
        "rpId": "proppertrading.com",
        "userVerification": "required",
        "allowCredentials": [{ "id": "AQIDBA", "type": "public-key" }]
    },
    "meta": { "request_id": "req_01kz4bkr1aj1gv63d8fv3133ey" }
}
```

The challenge inside these options is spent by the next answer, right or wrong.
Ask again rather than reusing them.

`rpId` is ours and stays ours whichever firm the account belongs to. The system
sheet on the phone names it, which is the trade this design makes.

## Errors

| Status | Code                           | Meaning                                                                     |
| ------ | ------------------------------ | --------------------------------------------------------------------------- |
| 401    | `expired_two_factor_challenge` | The sign-in challenge is gone, or the account has no passkey an app can use |
| 429    | `rate_limit_exceeded`          | Twenty attempts per ten minutes per address, shared with the answer itself  |
| 422    | `validation_failed`            | No challenge, or a malformed one                                            |

:::note One reason, two causes
An expired challenge and an account without an app passkey answer the same. From
outside there is one thing to do about either: start the sign-in again.
:::