# Auth · Customer API

> Customer API reference for auth: GET /auth/protection, POST /auth/challenge, POST /auth/verify, POST /auth/oauth/start, ….

> Markdown version of https://kanorio.com/en/developers/customer-api/auth. Append `.md` to any developer docs URL for this format. Index: https://kanorio.com/llms.txt · MCP: https://app.kanorio.com/api/mcp

Base URL: `https://{site}/api/v1/customer` · [Errors](https://kanorio.com/en/developers/basics/errors) · [Pagination](https://kanorio.com/en/developers/basics/pagination)

### Get the bot check to pass before requesting a login code

`GET /auth/protection`

- Stability: beta
- Authentication: none

**Response** (200)

| Field | Type | Required | Description |
| --- | --- | --- | --- |
| `provider` | "kanorio" \| "turnstile" \| "recaptcha" \| "hcaptcha" | yes | — |
| `entry` | "forms" \| "customer_login" | yes | — |
| `altcha` | object | no | Built-in proof-of-work challenge (provider kanorio only). |
| `altcha.algorithm` | string | yes | — |
| `altcha.challenge` | string | yes | — |
| `altcha.maxnumber` | integer | no | — |
| `altcha.salt` | string | yes | — |
| `altcha.signature` | string | yes | — |
| `siteKey` | string | no | Widget site key (third-party providers). |
| `action` | string | no | Pass this as the widget action; the server checks it. |
| `cData` | string \| null | no | Turnstile cData to pass to the widget; null for other providers. |
| `recaptchaVersion` | "v2" \| "v3" \| null | no | — |
| `visible` | boolean | no | When true, show the check to the visitor instead of running it silently (the site is seeing unusual sign-in volume). |
| `uploadToken` | string | no | Forms with a third-party provider only: send this as _tk (with _ctx set to the form id) when requesting file upload tickets. The widget token is single-use, so it is checked on submit instead. |

**Errors**

| Code | Status | When |
| --- | --- | --- |
| `WEBSITE_NOT_FOUND` | 404 | — |
| `RATE_LIMITED` | 429 | — |

```bash
curl -X GET "https://{site}/api/v1/customer/auth/protection"
```

### Request a login code / magic link by email

`POST /auth/challenge`

- Stability: beta
- Authentication: none

**Request body**

| Field | Type | Required | Description |
| --- | --- | --- | --- |
| `email` | string | yes | — |
| `locale` | string | no | — |
| `returnTo` | string | no | — |
| `_hp` | string | no | Honeypot. Send an empty string; any value marks the request as automated. |
| `_wp` | string | no | Second honeypot. Send an empty string. |
| `_co` | string | no | Third honeypot. Send an empty string. |
| `_ts` | integer | no | When the form was rendered, in Unix milliseconds. |
| `_tk` | string | no | Built-in bot check: the solved ALTCHA payload for the challenge (provider kanorio). |
| `_ctx` | string | no | What the built-in challenge was issued for: the form id, or customer-login:{siteId}. |
| `captchaToken` | string | no | Third-party bot check: the token from the provider's widget (provider turnstile, recaptcha or hcaptcha). |

**Errors**

| Code | Status | When |
| --- | --- | --- |
| `INVALID_EMAIL` | 400 | — |
| `BOT_PROTECTION_FAILED` | 403 | — |
| `WEBSITE_NOT_FOUND` | 404 | — |
| `SHARED_SENDER_PAUSED` | 503 | — |

```bash
curl -X POST "https://{site}/api/v1/customer/auth/challenge" \
  -H "Content-Type: application/json" \
  -d '{}'
```

### Exchange a code, magic-link token or social sign-in handoff ticket for a Bearer session

`POST /auth/verify`

- Stability: beta
- Authentication: none

**Request body**

| Field | Type | Required | Description |
| --- | --- | --- | --- |
| `method` | "code" | yes | — |
| `email` | string | yes | — |
| `code` | string | yes | — |

**Response** (200)

| Field | Type | Required | Description |
| --- | --- | --- | --- |
| `sessionToken` | string | yes | — |
| `tokenType` | "Bearer" | yes | — |
| `channel` | "bearer" | yes | — |
| `expiresAt` | string | yes | — |
| `contactId` | string | yes | — |
| `returnTo` | string \| null | no | — |

**Errors**

| Code | Status | When |
| --- | --- | --- |
| `INVALID_CODE` | 400 | — |
| `ACCOUNT_BLOCKED` | 422 | — |
| `EMAIL_NOT_VERIFIED` | 422 | — |
| `METHOD_DISABLED` | 422 | — |

```bash
curl -X POST "https://{site}/api/v1/customer/auth/verify" \
  -H "Content-Type: application/json" \
  -d '{}'
```

### Start a social sign-in (Google) and get the provider URL to redirect to

`POST /auth/oauth/start`

- Stability: beta
- Authentication: none

**Request body**

| Field | Type | Required | Description |
| --- | --- | --- | --- |
| `method` | "google" | yes | A social sign-in method the site has enabled (see `customerAccess.methods` on the site). |
| `returnTo` | string | no | Same-site path to return to after sign-in. |
| `locale` | string | no | — |
| `baseUrl` | string | no | Origin of the page that hosts the sign-in UI. Must be one of the site's allowed origins; the handoff ticket is sent back there. |

**Response** (200)

| Field | Type | Required | Description |
| --- | --- | --- | --- |
| `url` | string | yes | Redirect the browser here. The provider sends it back to your page with `?handoff=` to exchange at `/auth/verify`. |

**Errors**

| Code | Status | When |
| --- | --- | --- |
| `METHOD_DISABLED` | 422 | — |
| `RATE_LIMITED` | 429 | — |

```bash
curl -X POST "https://{site}/api/v1/customer/auth/oauth/start" \
  -H "Content-Type: application/json" \
  -d '{}'
```

### Get WebAuthn request options to sign in with a passkey

`POST /auth/passkey/options`

- Stability: beta
- Authentication: none

**Request body**

| Field | Type | Required | Description |
| --- | --- | --- | --- |
| `origin` | string | no | Origin the WebAuthn ceremony runs on. Only `localhost` origins are honoured; production always uses the site's primary domain. |

**Response** (200)

| Field | Type | Required | Description |
| --- | --- | --- | --- |
| `options` | object | yes | `PublicKeyCredentialRequestOptionsJSON` for `navigator.credentials.get()`. |
| `challengeId` | string | yes | — |

**Errors**

| Code | Status | When |
| --- | --- | --- |
| `METHOD_DISABLED` | 422 | — |
| `RATE_LIMITED` | 429 | — |

```bash
curl -X POST "https://{site}/api/v1/customer/auth/passkey/options" \
  -H "Content-Type: application/json" \
  -d '{}'
```

### Verify a passkey assertion and get a Bearer session

`POST /auth/passkey/verify`

- Stability: beta
- Authentication: none

**Request body**

| Field | Type | Required | Description |
| --- | --- | --- | --- |
| `challengeId` | string | yes | — |
| `response` | object | yes | `AuthenticationResponseJSON` from the browser. |
| `returnTo` | string | no | — |
| `locale` | string | no | — |

**Response** (200)

| Field | Type | Required | Description |
| --- | --- | --- | --- |
| `sessionToken` | string | yes | — |
| `tokenType` | "Bearer" | yes | — |
| `channel` | "bearer" | yes | — |
| `expiresAt` | string | yes | — |
| `contactId` | string | yes | — |
| `returnTo` | string \| null | no | — |

**Errors**

| Code | Status | When |
| --- | --- | --- |
| `VERIFY_FAILED` | 422 | — |
| `CHALLENGE_EXPIRED` | 422 | — |
| `ACCOUNT_BLOCKED` | 422 | — |
| `RATE_LIMITED` | 429 | — |

```bash
curl -X POST "https://{site}/api/v1/customer/auth/passkey/verify" \
  -H "Content-Type: application/json" \
  -d '{}'
```
