# Auth endpoints (Client API: account)

> 31 FeatherPanel Client API: account endpoints from user/auth.php: methods, paths, auth, permissions, rate limits, parameters and responses.

Source: https://featherpanel.com/docs/api/reference/user/auth/

Defined in [`backend/app/routes/user/auth.php`](https://github.com/MythicalLTD/FeatherPanel/blob/15b94e9fc9df233123706b47c42728b81e48cb3d/backend/app/routes/user/auth.php). Authentication used here: **Public**, **Signed-in user**. Read [Authentication](https://featherpanel.com/docs/api/authentication/) for how to send credentials and [Rate limits and errors](https://featherpanel.com/docs/api/rate-limits-and-errors/) for the response envelope.

Paths, methods, permissions and rate limits come from the route file. Summaries, parameters and response codes come from the OpenAPI annotations on the controllers; endpoints without annotations show only what the route file declares.

## Endpoints

### PUT `/api/user/auth/register`

- Auth: Public
- Default rate limit: 5 / minute (`user-auth`)
- Route name: `register`
- Controller: [`RegisterController::put`](https://github.com/MythicalLTD/FeatherPanel/blob/15b94e9fc9df233123706b47c42728b81e48cb3d/backend/app/Controllers/User/Auth/RegisterController.php)
- [route source](https://github.com/MythicalLTD/FeatherPanel/blob/15b94e9fc9df233123706b47c42728b81e48cb3d/backend/app/routes/user/auth.php#L36)

**Register new user** Create a new user account with email verification and welcome email. User is automatically logged in after successful registration. Includes CloudFlare Turnstile validation if enabled.

Request body: [`RegisterRequest`](https://featherpanel.com/docs/api/schemas/#schema-registerrequest) (application/json) · Response data: [`RegisterResponse`](https://featherpanel.com/docs/api/schemas/#schema-registerresponse)

| Body field | Type | Required | Description |
| --- | --- | --- | --- |
| `username` | string | yes | Username (alphanumeric and underscores only) |
| `email` | string (email) | yes | User email address |
| `password` | string | yes | User password |
| `first_name` | string | yes | User first name |
| `last_name` | string | yes | User last name |
| `turnstile_token` | string | no | CloudFlare Turnstile token (required if Turnstile is enabled) |

| Response `data` field | Type | Description |
| --- | --- | --- |
| `user` | object | User information |
| `preferences` | object | User preferences |
| `message` | string | Success message |

```bash
curl -X PUT "$PANEL_URL/api/user/auth/register" \
  -H "Accept: application/json" \
  -H "Content-Type: application/json" \
  -d '{"username":"","email":"","password":"","first_name":"","last_name":"","turnstile_token":""}'
```

Plugin events: [`featherpanel:auth:login:success`](https://featherpanel.com/docs/plugins/events/auth/#auth-login-success), [`featherpanel:auth:register:success`](https://featherpanel.com/docs/plugins/events/auth/#auth-register-success), [`featherpanel:auth:registration:failed`](https://featherpanel.com/docs/plugins/events/auth/#auth-registration-failed)

Responses: `200` User registered successfully and automatically logged in · `400` Bad request - Missing required fields, invalid data format, Turnstile validation failed, or Turnstile keys not set · `403` Forbidden - Registration is not enabled · `409` Conflict - Username or email already exists · `500` Internal server error - Failed to create user

### PUT `/api/user/auth/login`

- Auth: Public
- Default rate limit: 10 / minute (`user-auth`)
- Route name: `login`
- Controller: [`LoginController::put`](https://github.com/MythicalLTD/FeatherPanel/blob/15b94e9fc9df233123706b47c42728b81e48cb3d/backend/app/Controllers/User/Auth/LoginController.php)
- [route source](https://github.com/MythicalLTD/FeatherPanel/blob/15b94e9fc9df233123706b47c42728b81e48cb3d/backend/app/routes/user/auth.php#L49)

**Login user** Authenticate user with username or email and password. Includes CloudFlare Turnstile validation if enabled. Returns 2FA requirement if enabled.

Request body: [`LoginRequest`](https://featherpanel.com/docs/api/schemas/#schema-loginrequest) (application/json)

| Body field | Type | Required | Description |
| --- | --- | --- | --- |
| `username_or_email` | string | yes | User email address or username |
| `password` | string | yes | User password |
| `turnstile_token` | string | no | CloudFlare Turnstile token (required if Turnstile is enabled) |

```bash
curl -X PUT "$PANEL_URL/api/user/auth/login" \
  -H "Accept: application/json" \
  -H "Content-Type: application/json" \
  -d '{"username_or_email":"","password":"","turnstile_token":""}'
```

Plugin events: [`featherpanel:auth:login:failed`](https://featherpanel.com/docs/plugins/events/auth/#auth-login-failed)

Responses: `200` User logged in successfully or two-factor authentication required · `400` Bad request - Missing required fields, invalid username or email format, Turnstile validation failed, or Turnstile keys not set · `401` Unauthorized - Invalid credentials, user is banned, or authentication otherwise rejected · `500` Internal server error - Remember token not set

### PUT `/api/user/auth/forgot-password`

- Auth: Public
- Default rate limit: 5 / minute (`user-auth`)
- Route name: `forgot-password`
- Controller: [`ForgotPasswordController::put`](https://github.com/MythicalLTD/FeatherPanel/blob/15b94e9fc9df233123706b47c42728b81e48cb3d/backend/app/Controllers/User/Auth/ForgotPasswordController.php)
- [route source](https://github.com/MythicalLTD/FeatherPanel/blob/15b94e9fc9df233123706b47c42728b81e48cb3d/backend/app/routes/user/auth.php#L62)

**Request password reset** Send a password reset email to the user. Includes CloudFlare Turnstile validation if enabled.

Request body: [`ForgotPasswordRequest`](https://featherpanel.com/docs/api/schemas/#schema-forgotpasswordrequest) (application/json) · Response data: [`ForgotPasswordResponse`](https://featherpanel.com/docs/api/schemas/#schema-forgotpasswordresponse)

| Body field | Type | Required | Description |
| --- | --- | --- | --- |
| `email` | string (email) | yes | User email address |
| `turnstile_token` | string | no | CloudFlare Turnstile token (required if Turnstile is enabled) |

| Response `data` field | Type | Description |
| --- | --- | --- |
| `message` | string | Success message |

```bash
curl -X PUT "$PANEL_URL/api/user/auth/forgot-password" \
  -H "Accept: application/json" \
  -H "Content-Type: application/json" \
  -d '{"email":"","turnstile_token":""}'
```

Plugin events: [`featherpanel:auth:forgot:password`](https://featherpanel.com/docs/plugins/events/auth/#auth-forgot-password), [`featherpanel:auth:forgot:password:failed`](https://featherpanel.com/docs/plugins/events/auth/#auth-forgot-password-failed)

Responses: `200` Generic success whether or not the email belongs to an account, including when User::updateUser() fails (anti-enumeration). Reset email is queued asynchronously when an account exists. · `400` Bad request - Missing required fields, invalid email format, Turnstile validation failed, or Turnstile keys not set

### GET `/api/user/auth/reset-password`

- Auth: Public
- Default rate limit: 10 / minute (`user-auth`)
- Route name: `reset-password-get`
- Controller: [`ResetPasswordController::get`](https://github.com/MythicalLTD/FeatherPanel/blob/15b94e9fc9df233123706b47c42728b81e48cb3d/backend/app/Controllers/User/Auth/ResetPasswordController.php)
- [route source](https://github.com/MythicalLTD/FeatherPanel/blob/15b94e9fc9df233123706b47c42728b81e48cb3d/backend/app/routes/user/auth.php#L75)

**Validate reset token** Validate a password reset token to check if it is valid before allowing password reset.

| Parameter | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `token` | query | string | yes | Password reset token from email |

Response data: [`TokenValidationResponse`](https://featherpanel.com/docs/api/schemas/#schema-tokenvalidationresponse)

| Response `data` field | Type | Description |
| --- | --- | --- |
| `message` | string | Token validation message |
| `token` | string | Validated token |

```bash
curl -X GET "$PANEL_URL/api/user/auth/reset-password?token=$TOKEN" \
  -H "Accept: application/json"
```

Responses: `200` Token is valid · `400` Bad request - Missing token or invalid token · `500` Internal server error - Failed to validate token

### GET `/api/user/auth/verify-email`

- Auth: Public
- Default rate limit: 10 / minute (`user-auth`)
- Route name: `verify-email-get`
- Controller: [`VerifyEmailController::get`](https://github.com/MythicalLTD/FeatherPanel/blob/15b94e9fc9df233123706b47c42728b81e48cb3d/backend/app/Controllers/User/Auth/VerifyEmailController.php)
- [route source](https://github.com/MythicalLTD/FeatherPanel/blob/15b94e9fc9df233123706b47c42728b81e48cb3d/backend/app/routes/user/auth.php#L88)

```bash
curl -X GET "$PANEL_URL/api/user/auth/verify-email" \
  -H "Accept: application/json"
```

### POST `/api/user/auth/verify-email/resend`

- Auth: Public
- Default rate limit: 3 / minute (`user-auth-verify-email`)
- Route name: `verify-email-resend`
- Controller: [`VerifyEmailController::resend`](https://github.com/MythicalLTD/FeatherPanel/blob/15b94e9fc9df233123706b47c42728b81e48cb3d/backend/app/Controllers/User/Auth/VerifyEmailController.php)
- [route source](https://github.com/MythicalLTD/FeatherPanel/blob/15b94e9fc9df233123706b47c42728b81e48cb3d/backend/app/routes/user/auth.php#L101)

```bash
curl -X POST "$PANEL_URL/api/user/auth/verify-email/resend" \
  -H "Accept: application/json"
```

### PUT `/api/user/auth/reset-password`

- Auth: Public
- Default rate limit: 5 / minute (`user-auth`)
- Route name: `reset-password-put`
- Controller: [`ResetPasswordController::put`](https://github.com/MythicalLTD/FeatherPanel/blob/15b94e9fc9df233123706b47c42728b81e48cb3d/backend/app/Controllers/User/Auth/ResetPasswordController.php)
- [route source](https://github.com/MythicalLTD/FeatherPanel/blob/15b94e9fc9df233123706b47c42728b81e48cb3d/backend/app/routes/user/auth.php#L114)

**Reset password** Reset user password using token from forgot password email. Includes CloudFlare Turnstile validation if enabled.

Request body: [`ResetPasswordRequest`](https://featherpanel.com/docs/api/schemas/#schema-resetpasswordrequest) (application/json) · Response data: [`ResetPasswordResponse`](https://featherpanel.com/docs/api/schemas/#schema-resetpasswordresponse)

| Body field | Type | Required | Description |
| --- | --- | --- | --- |
| `token` | string | yes | Password reset token from email |
| `password` | string | yes | New password |
| `turnstile_token` | string | no | CloudFlare Turnstile token (required if Turnstile is enabled) |

| Response `data` field | Type | Description |
| --- | --- | --- |
| `message` | string | Success message |

```bash
curl -X PUT "$PANEL_URL/api/user/auth/reset-password" \
  -H "Accept: application/json" \
  -H "Content-Type: application/json" \
  -d '{"token":"","password":"","turnstile_token":""}'
```

Plugin events: [`featherpanel:auth:password:reset:failed`](https://featherpanel.com/docs/plugins/events/auth/#auth-password-reset-failed), [`featherpanel:auth:reset:password:success`](https://featherpanel.com/docs/plugins/events/auth/#auth-reset-password-success)

Responses: `200` Password reset successfully · `400` Bad request - Missing required fields, Turnstile validation failed, or Turnstile keys not set · `404` Not found - Invalid token · `500` Internal server error - Failed to reset password

### PUT `/api/user/auth/two-factor`

- Auth: Signed-in user
- Default rate limit: 10 / minute (`user-auth`)
- Route name: `two-factor`
- Controller: [`TwoFactorController::put`](https://github.com/MythicalLTD/FeatherPanel/blob/15b94e9fc9df233123706b47c42728b81e48cb3d/backend/app/Controllers/User/Auth/TwoFactorController.php)
- [route source](https://github.com/MythicalLTD/FeatherPanel/blob/15b94e9fc9df233123706b47c42728b81e48cb3d/backend/app/routes/user/auth.php#L127)

**Enable two-factor authentication** Enable two-factor authentication for the authenticated user using TOTP code verification. Includes CloudFlare Turnstile validation if enabled.

Request body: [`TwoFactorEnableRequest`](https://featherpanel.com/docs/api/schemas/#schema-twofactorenablerequest) (application/json) · Response data: [`TwoFactorEnableResponse`](https://featherpanel.com/docs/api/schemas/#schema-twofactorenableresponse)

| Body field | Type | Required | Description |
| --- | --- | --- | --- |
| `code` | string | yes | 6-digit TOTP code |
| `secret` | string | yes | 16-character secret key |
| `turnstile_token` | string | no | CloudFlare Turnstile token (required if Turnstile is enabled) |

| Response `data` field | Type | Description |
| --- | --- | --- |
| `user` | object | User information |
| `message` | string | Success message |

```bash
curl -X PUT "$PANEL_URL/api/user/auth/two-factor" \
  -H "Authorization: Bearer $FEATHERPANEL_API_KEY" \
  -H "Accept: application/json" \
  -H "Content-Type: application/json" \
  -d '{"code":"","secret":"","turnstile_token":""}'
```

Plugin events: [`featherpanel:auth:2fa:enabled`](https://featherpanel.com/docs/plugins/events/auth/#auth2-faenabled)

Responses: `200` Two-factor authentication enabled successfully · `400` Bad request - Missing required fields, invalid code length, Turnstile validation failed, or Turnstile keys not set · `401` Unauthorized - Invalid TOTP code or user is banned · `500` Internal server error - Failed to enable 2FA

### GET `/api/user/auth/two-factor`

- Auth: Signed-in user
- Route name: `two-factor-get`
- Controller: [`TwoFactorController::get`](https://github.com/MythicalLTD/FeatherPanel/blob/15b94e9fc9df233123706b47c42728b81e48cb3d/backend/app/Controllers/User/Auth/TwoFactorController.php)
- [route source](https://github.com/MythicalLTD/FeatherPanel/blob/15b94e9fc9df233123706b47c42728b81e48cb3d/backend/app/routes/user/auth.php#L140)

**Get two-factor setup** Get QR code URL and secret key for setting up two-factor authentication with an authenticator app.

Response data: [`TwoFactorSetupResponse`](https://featherpanel.com/docs/api/schemas/#schema-twofactorsetupresponse)

| Response `data` field | Type | Description |
| --- | --- | --- |
| `qr_code_url` | string | QR code URL for authenticator app |
| `secret` | string | Secret key for manual entry |
| `message` | string | Success message |

```bash
curl -X GET "$PANEL_URL/api/user/auth/two-factor" \
  -H "Authorization: Bearer $FEATHERPANEL_API_KEY" \
  -H "Accept: application/json"
```

Plugin events: [`featherpanel:auth:2fa:setup`](https://featherpanel.com/docs/plugins/events/auth/#auth2-fasetup)

Responses: `200` Two-factor setup information retrieved successfully · `400` Bad request - Two-factor authentication already enabled · `401` Unauthorized - User not authenticated · `500` Internal server error - Failed to generate setup information

### GET `/api/user/auth/logout`

- Auth: Public
- Default rate limit: 30 / minute (`user-auth`)
- Route name: `auth-logout`
- Controller: [`AuthLogoutController::get`](https://github.com/MythicalLTD/FeatherPanel/blob/15b94e9fc9df233123706b47c42728b81e48cb3d/backend/app/Controllers/User/Auth/AuthLogoutController.php)
- [route source](https://github.com/MythicalLTD/FeatherPanel/blob/15b94e9fc9df233123706b47c42728b81e48cb3d/backend/app/routes/user/auth.php#L150)

**Logout user** Logout the authenticated user by clearing remember token cookie and updating user session. Blocks 2FA if enabled.

Response data: [`LogoutResponse`](https://featherpanel.com/docs/api/schemas/#schema-logoutresponse)

| Response `data` field | Type | Description |
| --- | --- | --- |
| `message` | string | Logout success message |

```bash
curl -X GET "$PANEL_URL/api/user/auth/logout" \
  -H "Accept: application/json"
```

Plugin events: [`featherpanel:auth:logout`](https://featherpanel.com/docs/plugins/events/auth/#auth-logout)

Responses: `200` User logged out successfully · `500` Internal server error - Failed to logout user

### DELETE `/api/user/auth/logout`

- Auth: Public
- Default rate limit: 30 / minute (`user-auth`)
- Route name: `auth-logout`
- Controller: [`AuthLogoutController::get`](https://github.com/MythicalLTD/FeatherPanel/blob/15b94e9fc9df233123706b47c42728b81e48cb3d/backend/app/Controllers/User/Auth/AuthLogoutController.php)
- [route source](https://github.com/MythicalLTD/FeatherPanel/blob/15b94e9fc9df233123706b47c42728b81e48cb3d/backend/app/routes/user/auth.php#L150)

**Logout user** Logout the authenticated user by clearing remember token cookie and updating user session. Blocks 2FA if enabled.

Response data: [`LogoutResponse`](https://featherpanel.com/docs/api/schemas/#schema-logoutresponse)

| Response `data` field | Type | Description |
| --- | --- | --- |
| `message` | string | Logout success message |

```bash
curl -X DELETE "$PANEL_URL/api/user/auth/logout" \
  -H "Accept: application/json"
```

Plugin events: [`featherpanel:auth:logout`](https://featherpanel.com/docs/plugins/events/auth/#auth-logout)

Responses: `200` User logged out successfully · `500` Internal server error - Failed to logout user

### POST `/api/user/auth/two-factor`

- Auth: Public
- Default rate limit: 10 / minute (`user-auth`)
- Route name: `auth-two-factor-post`
- Controller: [`TwoFactorController::post`](https://github.com/MythicalLTD/FeatherPanel/blob/15b94e9fc9df233123706b47c42728b81e48cb3d/backend/app/Controllers/User/Auth/TwoFactorController.php)
- [route source](https://github.com/MythicalLTD/FeatherPanel/blob/15b94e9fc9df233123706b47c42728b81e48cb3d/backend/app/routes/user/auth.php#L162)

**Verify two-factor code** Verify two-factor authentication code during login process to complete authentication.

Request body: [`TwoFactorVerifyRequest`](https://featherpanel.com/docs/api/schemas/#schema-twofactorverifyrequest) (application/json) · Response data: [`TwoFactorVerifyResponse`](https://featherpanel.com/docs/api/schemas/#schema-twofactorverifyresponse)

| Body field | Type | Required | Description |
| --- | --- | --- | --- |
| `email` | string (email) | yes | User email address |
| `code` | string | yes | 6-digit TOTP code |
| `challenge` | string | yes | Challenge token from the password authentication step (TWO\_FACTOR\_REQUIRED) |

| Response `data` field | Type | Description |
| --- | --- | --- |
| `user` | object | User information |
| `message` | string | Success message |

```bash
curl -X POST "$PANEL_URL/api/user/auth/two-factor" \
  -H "Accept: application/json" \
  -H "Content-Type: application/json" \
  -d '{"email":"","code":"","challenge":""}'
```

Plugin events: [`featherpanel:auth:2fa:failed`](https://featherpanel.com/docs/plugins/events/auth/#auth2-fafailed), [`featherpanel:auth:2fa:verified`](https://featherpanel.com/docs/plugins/events/auth/#auth2-faverified), [`featherpanel:auth:login:success`](https://featherpanel.com/docs/plugins/events/auth/#auth-login-success)

Responses: `200` Two-factor authentication verified successfully · `400` Bad request - Missing email, code, or challenge · `401` Unauthorized - 2FA not enabled, invalid code, or missing/invalid challenge · `429` Too many failed 2FA attempts for this account · `500` Internal server error - Failed to verify 2FA

### GET `/api/user/auth/discord/login`

- Auth: Public
- Route name: `discord-login`
- Controller: [`DiscordController::login`](https://github.com/MythicalLTD/FeatherPanel/blob/15b94e9fc9df233123706b47c42728b81e48cb3d/backend/app/Controllers/User/Auth/DiscordController.php)
- [route source](https://github.com/MythicalLTD/FeatherPanel/blob/15b94e9fc9df233123706b47c42728b81e48cb3d/backend/app/routes/user/auth.php#L175)

```bash
curl -X GET "$PANEL_URL/api/user/auth/discord/login" \
  -H "Accept: application/json"
```

### GET `/api/user/auth/discord/callback`

- Auth: Public
- Default rate limit: 10 / minute (`user-auth-discord`)
- Route name: `discord-callback`
- Controller: [`DiscordController::callback`](https://github.com/MythicalLTD/FeatherPanel/blob/15b94e9fc9df233123706b47c42728b81e48cb3d/backend/app/Controllers/User/Auth/DiscordController.php)
- [route source](https://github.com/MythicalLTD/FeatherPanel/blob/15b94e9fc9df233123706b47c42728b81e48cb3d/backend/app/routes/user/auth.php#L185)

```bash
curl -X GET "$PANEL_URL/api/user/auth/discord/callback" \
  -H "Accept: application/json"
```

### GET `/api/user/auth/discord/link`

- Auth: Signed-in user
- Default rate limit: 5 / minute (`user-auth-discord`)
- Route name: `discord-link-initiate`
- Controller: [`DiscordController::linkAccount`](https://github.com/MythicalLTD/FeatherPanel/blob/15b94e9fc9df233123706b47c42728b81e48cb3d/backend/app/Controllers/User/Auth/DiscordController.php)
- [route source](https://github.com/MythicalLTD/FeatherPanel/blob/15b94e9fc9df233123706b47c42728b81e48cb3d/backend/app/routes/user/auth.php#L197)

```bash
curl -X GET "$PANEL_URL/api/user/auth/discord/link" \
  -H "Authorization: Bearer $FEATHERPANEL_API_KEY" \
  -H "Accept: application/json"
```

### PUT `/api/user/auth/discord/link`

- Auth: Public
- Default rate limit: 5 / minute (`user-auth-discord`)
- Route name: `discord-link`
- Controller: [`DiscordController::link`](https://github.com/MythicalLTD/FeatherPanel/blob/15b94e9fc9df233123706b47c42728b81e48cb3d/backend/app/Controllers/User/Auth/DiscordController.php)
- [route source](https://github.com/MythicalLTD/FeatherPanel/blob/15b94e9fc9df233123706b47c42728b81e48cb3d/backend/app/routes/user/auth.php#L209)

```bash
curl -X PUT "$PANEL_URL/api/user/auth/discord/link" \
  -H "Accept: application/json"
```

### PUT `/api/user/auth/discord/register`

- Auth: Public
- Default rate limit: 5 / minute (`user-auth-discord`)
- Route name: `discord-register`
- Controller: [`DiscordController::register`](https://github.com/MythicalLTD/FeatherPanel/blob/15b94e9fc9df233123706b47c42728b81e48cb3d/backend/app/Controllers/User/Auth/DiscordController.php)
- [route source](https://github.com/MythicalLTD/FeatherPanel/blob/15b94e9fc9df233123706b47c42728b81e48cb3d/backend/app/routes/user/auth.php#L221)

```bash
curl -X PUT "$PANEL_URL/api/user/auth/discord/register" \
  -H "Accept: application/json"
```

### DELETE `/api/user/auth/discord/unlink`

- Auth: Signed-in user
- Default rate limit: 5 / minute (`user-auth-discord`)
- Route name: `discord-unlink`
- Controller: [`DiscordController::unlink`](https://github.com/MythicalLTD/FeatherPanel/blob/15b94e9fc9df233123706b47c42728b81e48cb3d/backend/app/Controllers/User/Auth/DiscordController.php)
- [route source](https://github.com/MythicalLTD/FeatherPanel/blob/15b94e9fc9df233123706b47c42728b81e48cb3d/backend/app/routes/user/auth.php#L233)

```bash
curl -X DELETE "$PANEL_URL/api/user/auth/discord/unlink" \
  -H "Authorization: Bearer $FEATHERPANEL_API_KEY" \
  -H "Accept: application/json"
```

### GET `/api/user/auth/oidc/login`

- Auth: Public
- Default rate limit: 10 / minute (`user-auth-oidc`)
- Route name: `oidc-login`
- Controller: [`OidcController::login`](https://github.com/MythicalLTD/FeatherPanel/blob/15b94e9fc9df233123706b47c42728b81e48cb3d/backend/app/Controllers/User/Auth/OidcController.php)
- [route source](https://github.com/MythicalLTD/FeatherPanel/blob/15b94e9fc9df233123706b47c42728b81e48cb3d/backend/app/routes/user/auth.php#L246)

**Initiate OIDC login** Redirects the user to the configured OpenID Connect (OIDC) provider.

```bash
curl -X GET "$PANEL_URL/api/user/auth/oidc/login" \
  -H "Accept: application/json"
```

### GET `/api/user/auth/oidc/callback`

- Auth: Public
- Default rate limit: 10 / minute (`user-auth-oidc`)
- Route name: `oidc-callback`
- Controller: [`OidcController::callback`](https://github.com/MythicalLTD/FeatherPanel/blob/15b94e9fc9df233123706b47c42728b81e48cb3d/backend/app/Controllers/User/Auth/OidcController.php)
- [route source](https://github.com/MythicalLTD/FeatherPanel/blob/15b94e9fc9df233123706b47c42728b81e48cb3d/backend/app/routes/user/auth.php#L258)

**OIDC callback** Handles the OpenID Connect (OIDC) callback and logs the user in.

```bash
curl -X GET "$PANEL_URL/api/user/auth/oidc/callback" \
  -H "Accept: application/json"
```

### GET `/api/user/auth/oidc/link`

- Auth: Signed-in user
- Default rate limit: 5 / minute (`user-auth-oidc`)
- Route name: `oidc-link`
- Controller: [`OidcController::link`](https://github.com/MythicalLTD/FeatherPanel/blob/15b94e9fc9df233123706b47c42728b81e48cb3d/backend/app/Controllers/User/Auth/OidcController.php)
- [route source](https://github.com/MythicalLTD/FeatherPanel/blob/15b94e9fc9df233123706b47c42728b81e48cb3d/backend/app/routes/user/auth.php#L270)

```bash
curl -X GET "$PANEL_URL/api/user/auth/oidc/link" \
  -H "Authorization: Bearer $FEATHERPANEL_API_KEY" \
  -H "Accept: application/json"
```

### DELETE `/api/user/auth/oidc/unlink`

- Auth: Signed-in user
- Default rate limit: 5 / minute (`user-auth-oidc`)
- Route name: `oidc-unlink`
- Controller: [`OidcController::unlink`](https://github.com/MythicalLTD/FeatherPanel/blob/15b94e9fc9df233123706b47c42728b81e48cb3d/backend/app/Controllers/User/Auth/OidcController.php)
- [route source](https://github.com/MythicalLTD/FeatherPanel/blob/15b94e9fc9df233123706b47c42728b81e48cb3d/backend/app/routes/user/auth.php#L282)

```bash
curl -X DELETE "$PANEL_URL/api/user/auth/oidc/unlink" \
  -H "Authorization: Bearer $FEATHERPANEL_API_KEY" \
  -H "Accept: application/json"
```

### POST `/api/user/auth/email-login/request`

- Auth: Public
- Default rate limit: 3 / minute (`user-auth-email`)
- Route name: `email-login-request`
- Controller: [`EmailLoginController::requestCode`](https://github.com/MythicalLTD/FeatherPanel/blob/15b94e9fc9df233123706b47c42728b81e48cb3d/backend/app/Controllers/User/Auth/EmailLoginController.php)
- [route source](https://github.com/MythicalLTD/FeatherPanel/blob/15b94e9fc9df233123706b47c42728b81e48cb3d/backend/app/routes/user/auth.php#L295)

**Request email login code** Send a 6-digit login code to the user's email address for passwordless authentication.

Request body: [`EmailLoginRequest`](https://featherpanel.com/docs/api/schemas/#schema-emailloginrequest) (application/json) · Response data: [`EmailLoginCodeSentResponse`](https://featherpanel.com/docs/api/schemas/#schema-emaillogincodesentresponse)

| Body field | Type | Required | Description |
| --- | --- | --- | --- |
| `email` | string (email) | yes | User email address |
| `turnstile_token` | string | no | CloudFlare Turnstile token (required if Turnstile is enabled) |

| Response `data` field | Type | Description |
| --- | --- | --- |
| `message` | string | Success message |
| `email` | string | Email address where code was sent |

```bash
curl -X POST "$PANEL_URL/api/user/auth/email-login/request" \
  -H "Accept: application/json" \
  -H "Content-Type: application/json" \
  -d '{"email":"","turnstile_token":""}'
```

Plugin events: [`featherpanel:auth:email:login:code:requested`](https://featherpanel.com/docs/plugins/events/auth/#auth-email-login-code-requested), [`featherpanel:auth:login:failed`](https://featherpanel.com/docs/plugins/events/auth/#auth-login-failed)

Responses: `200` Login code sent successfully · `400` Bad request - Missing email, invalid format, or Turnstile validation failed · `401` Unauthorized - Email not found or user is banned · `403` Forbidden - Email login is disabled · `429` Too many requests - Rate limited

### POST `/api/user/auth/email-login/verify`

- Auth: Public
- Default rate limit: 10 / minute (`user-auth-email`)
- Route name: `email-login-verify`
- Controller: [`EmailLoginController::verifyCode`](https://github.com/MythicalLTD/FeatherPanel/blob/15b94e9fc9df233123706b47c42728b81e48cb3d/backend/app/Controllers/User/Auth/EmailLoginController.php)
- [route source](https://github.com/MythicalLTD/FeatherPanel/blob/15b94e9fc9df233123706b47c42728b81e48cb3d/backend/app/routes/user/auth.php#L307)

**Verify email login code** Verify the 6-digit login code and authenticate the user.

Request body: [`EmailLoginVerifyRequest`](https://featherpanel.com/docs/api/schemas/#schema-emailloginverifyrequest) (application/json) · Response data: [`LoginResponse`](https://featherpanel.com/docs/api/schemas/#schema-loginresponse)

| Body field | Type | Required | Description |
| --- | --- | --- | --- |
| `email` | string (email) | yes | User email address |
| `code` | string | yes | 6-digit login code sent to email |

| Response `data` field | Type | Description |
| --- | --- | --- |
| `user` | object | User information |
| `preferences` | object | User preferences |
| `message` | string | Success message |

```bash
curl -X POST "$PANEL_URL/api/user/auth/email-login/verify" \
  -H "Accept: application/json" \
  -H "Content-Type: application/json" \
  -d '{"email":"","code":""}'
```

Plugin events: [`featherpanel:auth:login:failed`](https://featherpanel.com/docs/plugins/events/auth/#auth-login-failed)

Responses: `200` User logged in successfully · `400` Bad request - Missing fields or invalid code format · `401` Unauthorized - Invalid or expired code · `403` Forbidden - Email login is disabled

### POST `/api/user/auth/qr/start`

- Auth: Public
- Default rate limit: 30 / minute (`user-auth-qr-start`)
- Route name: `qr-login-start`
- Controller: [`QrLoginController::start`](https://github.com/MythicalLTD/FeatherPanel/blob/15b94e9fc9df233123706b47c42728b81e48cb3d/backend/app/Controllers/User/Auth/QrLoginController.php)
- [route source](https://github.com/MythicalLTD/FeatherPanel/blob/15b94e9fc9df233123706b47c42728b81e48cb3d/backend/app/routes/user/auth.php#L320)

**Start QR login challenge** Creates a short-lived QR login challenge for Discord-style sign-in from a phone that is already logged in.

```bash
curl -X POST "$PANEL_URL/api/user/auth/qr/start" \
  -H "Accept: application/json"
```

Responses: `200` Challenge created · `429` Rate limited

### POST `/api/user/auth/qr/poll`

- Auth: Public
- Default rate limit: 60 / minute (`user-auth-qr-poll`)
- Route name: `qr-login-poll`
- Controller: [`QrLoginController::poll`](https://github.com/MythicalLTD/FeatherPanel/blob/15b94e9fc9df233123706b47c42728b81e48cb3d/backend/app/Controllers/User/Auth/QrLoginController.php)
- [route source](https://github.com/MythicalLTD/FeatherPanel/blob/15b94e9fc9df233123706b47c42728b81e48cb3d/backend/app/routes/user/auth.php#L332)

**Poll QR login challenge** Desktop polls challenge status. When approved, returns a one-time exchange\_token.

```bash
curl -X POST "$PANEL_URL/api/user/auth/qr/poll" \
  -H "Accept: application/json"
```

Responses: `200` Challenge status · `400` Invalid challenge or secret · `429` Polling too fast

### POST `/api/user/auth/qr/exchange`

- Auth: Public
- Default rate limit: 30 / minute (`user-auth-qr-exchange`)
- Route name: `qr-login-exchange`
- Controller: [`QrLoginController::exchange`](https://github.com/MythicalLTD/FeatherPanel/blob/15b94e9fc9df233123706b47c42728b81e48cb3d/backend/app/Controllers/User/Auth/QrLoginController.php)
- [route source](https://github.com/MythicalLTD/FeatherPanel/blob/15b94e9fc9df233123706b47c42728b81e48cb3d/backend/app/routes/user/auth.php#L344)

**Exchange QR login token for session** Desktop redeems the one-time exchange\_token and receives the remember\_token cookie via completeLogin.

```bash
curl -X POST "$PANEL_URL/api/user/auth/qr/exchange" \
  -H "Accept: application/json"
```

Responses: `200` Logged in · `400` Invalid or expired token · `403` User banned or deleted

### GET `/api/user/auth/qr/code/{user_code}`

- Auth: Signed-in user
- Default rate limit: 30 / minute (`user-auth-qr-approval`)
- Route name: `qr-login-show-by-code`
- Controller: [`QrLoginController::showByCode`](https://github.com/MythicalLTD/FeatherPanel/blob/15b94e9fc9df233123706b47c42728b81e48cb3d/backend/app/Controllers/User/Auth/QrLoginController.php)
- [route source](https://github.com/MythicalLTD/FeatherPanel/blob/15b94e9fc9df233123706b47c42728b81e48cb3d/backend/app/routes/user/auth.php#L356)

**Look up QR login challenge by user code** Authenticated user enters the short code shown on another device to load and mark the challenge as scanned.

| Parameter | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `user_code` | path | \- | yes |  |

```bash
curl -X GET "$PANEL_URL/api/user/auth/qr/code/$USER_CODE" \
  -H "Authorization: Bearer $FEATHERPANEL_API_KEY" \
  -H "Accept: application/json"
```

Responses: `200` Challenge details · `400` Invalid or expired code · `401` Unauthorized

### GET `/api/user/auth/qr/{challenge_id}`

- Auth: Signed-in user
- Default rate limit: 30 / minute (`user-auth-qr-approval`)
- Route name: `qr-login-show`
- Controller: [`QrLoginController::show`](https://github.com/MythicalLTD/FeatherPanel/blob/15b94e9fc9df233123706b47c42728b81e48cb3d/backend/app/Controllers/User/Auth/QrLoginController.php)
- [route source](https://github.com/MythicalLTD/FeatherPanel/blob/15b94e9fc9df233123706b47c42728b81e48cb3d/backend/app/routes/user/auth.php#L368)

**Get QR login challenge for approval** Authenticated phone loads challenge details and marks it as scanned.

| Parameter | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `challenge_id` | path | \- | yes |  |

```bash
curl -X GET "$PANEL_URL/api/user/auth/qr/$CHALLENGE_ID" \
  -H "Authorization: Bearer $FEATHERPANEL_API_KEY" \
  -H "Accept: application/json"
```

Responses: `200` Challenge details · `400` Invalid or expired challenge · `401` Unauthorized

### POST `/api/user/auth/qr/{challenge_id}/approve`

- Auth: Signed-in user
- Default rate limit: 30 / minute (`user-auth-qr-approval`)
- Route name: `qr-login-approve`
- Controller: [`QrLoginController::approve`](https://github.com/MythicalLTD/FeatherPanel/blob/15b94e9fc9df233123706b47c42728b81e48cb3d/backend/app/Controllers/User/Auth/QrLoginController.php)
- [route source](https://github.com/MythicalLTD/FeatherPanel/blob/15b94e9fc9df233123706b47c42728b81e48cb3d/backend/app/routes/user/auth.php#L380)

**Approve QR login challenge** Authenticated phone approves the desktop login. Does not set a cookie on the phone; mints an exchange token for the desktop.

| Parameter | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `challenge_id` | path | \- | yes |  |

```bash
curl -X POST "$PANEL_URL/api/user/auth/qr/$CHALLENGE_ID/approve" \
  -H "Authorization: Bearer $FEATHERPANEL_API_KEY" \
  -H "Accept: application/json"
```

Responses: `200` Approved · `400` Invalid challenge · `401` Unauthorized · `403` User banned or deleted

### POST `/api/user/auth/qr/{challenge_id}/deny`

- Auth: Signed-in user
- Default rate limit: 30 / minute (`user-auth-qr-approval`)
- Route name: `qr-login-deny`
- Controller: [`QrLoginController::deny`](https://github.com/MythicalLTD/FeatherPanel/blob/15b94e9fc9df233123706b47c42728b81e48cb3d/backend/app/Controllers/User/Auth/QrLoginController.php)
- [route source](https://github.com/MythicalLTD/FeatherPanel/blob/15b94e9fc9df233123706b47c42728b81e48cb3d/backend/app/routes/user/auth.php#L392)

**Deny QR login challenge**

| Parameter | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `challenge_id` | path | \- | yes |  |

```bash
curl -X POST "$PANEL_URL/api/user/auth/qr/$CHALLENGE_ID/deny" \
  -H "Authorization: Bearer $FEATHERPANEL_API_KEY" \
  -H "Accept: application/json"
```

Responses: `200` Denied · `400` Invalid challenge · `401` Unauthorized
