# Authentication

**Page:** api/authentication

[Download Raw Markdown](./api/authentication.md)

---

{/* AUTO-GENERATED — Do not edit manually. Regenerate with: npm run docs:api:generate */}



Authenticate users with the Hoody API, manage sessions, verify email addresses, and configure two-factor authentication. This page covers email/password flows, OAuth (GitHub and Google), PKCE-protected popup handoffs, email verification, password recovery, and TOTP-based 2FA.


All authenticated endpoints expect a JWT in the `Authorization` header as `Bearer &lt;token&gt;`. Access tokens expire in 1 day and refresh tokens in 7 days. Response bodies are ED25519-signed via the `X-Hoody-Signature` header — fetch the public key from `/api/v1/meta/public-key` to verify them.


## Server discovery

### `GET /api/v1/auth/available-regions`

Returns regions where free-tier servers exist, with boolean availability. This endpoint is public and requires no authentication.



```bash
curl https://api.hoody.com/api/v1/auth/available-regions
```


```typescript
const { data } = await client.api.authentication.getAvailableRegions();
```


```json
{
  "statusCode": 200,
  "data": {
    "regions": [
      {
        "region": "eu-west",
        "country": "Netherlands",
        "city": "Amsterdam",
        "available": true
      },
      {
        "region": "us-east",
        "country": "United States",
        "city": "New York",
        "available": true
      },
      {
        "region": "ap-southeast",
        "country": "Singapore",
        "city": "Singapore",
        "available": false
      }
    ]
  }
}
```



## Signing keys

### `GET /api/v1/meta/public-key`

Returns the ED25519 public keys used by Hoody to sign API responses (`X-Hoody-Signature` header), identity claims issued at login, and container authorization claims. No authentication required.

**Verification flow:**

1. Fetch this endpoint once and cache the result for at least 24 hours.
2. Locate the key by `kid` from the `keys[]` array.
3. For response signatures, parse `X-Hoody-Signature: t=&lt;ts&gt;,kid=&lt;id&gt;,path=&lt;url&gt;,sig=&lt;hex&gt;` and verify `sig` against `t + "." + responseBody`.
4. For identity and container claims, verify `claim.signature_hex` against the UTF-8 bytes of `claim.payload_b64`.
5. If a signature references a `kid` not present in your cached keys, re-fetch this endpoint.



```bash
curl https://api.hoody.com/api/v1/meta/public-key
```


```typescript
const { data } = await client.api.meta.getPublicKey();
```


```json
{
  "statusCode": 200,
  "message": "Hoody API signing public key",
  "data": {
    "keys": [
      {
        "kid": "v1",
        "algorithm": "ed25519",
        "public_key_hex": "8c8d683c125761bd9157e3a6f98c30d81cd7f2be4d16062a8342d1fcd2ca474a",
        "public_key_b64": "jI1oPBJXYb2RV+Om+YwwwlzX8r5NFgYqg0LRzSykd0o=",
        "public_key_b64url": "jI1oPBJXYb2RV-Om-YwwwlzX8r5NFgYqg0LRzSykd0o"
      }
    ],
    "active_kid": "v1",
    "usage": ["response-signing", "identity-claims", "container-claims"],
    "signing_format": {
      "response_header": "X-Hoody-Signature: t=<unix_ts>,kid=<key_id>,path=<request_url>,sig=<hex>",
      "response_signed_data": "<t_value>.<response_body_utf8_string>",
      "identity_claim_signed_data": "base64url(JSON.stringify(claim_payload)) — the b64url string itself (UTF-8 bytes)",
      "container_claim_signed_data": "base64url(JSON.stringify(container_claim_payload)) — the b64url string itself (UTF-8 bytes)",
      "replay_tolerance_seconds": 300
    }
  }
}
```

Responses include a `X-Hoody-Signature` header containing the ED25519 signature in the format `t=&lt;unix_ts&gt;,kid=&lt;keyId&gt;,path=&lt;urlPath&gt;,sig=&lt;128-hex&gt;`.


```json
{
  "statusCode": 503,
  "error": "SIGNING_NOT_CONFIGURED",
  "message": "Response signing is not configured on this API instance"
}
```



## OAuth authentication

All OAuth redirect endpoints use PKCE. The `code_challenge` (base64url SHA-256 of `code_verifier`) is required.

### `GET /api/v1/auth/github`

Redirects the browser to GitHub for OAuth authentication. Browser-only endpoint.

#### Parameters

| Name | In | Type | Required | Description |
|------|-----|------|----------|-------------|
| `intent` | query | string | No | OAuth intent: `login` (default) or `star_check` (check for star credit). Allowed values: `login`, `star_check`. |
| `redirect_uri` | query | string | No | Frontend URL to redirect to after OAuth completes (must be on allowed domain) |
| `code_challenge` | query | string | Yes | PKCE `code_challenge` (base64url SHA-256 of `code_verifier`). Required — all OAuth flows must use PKCE post-migration. |



```bash
curl -L "https://api.hoody.com/api/v1/auth/github?intent=login&code_challenge=E9Melhoa2OwvFrEMTJguCHaoeK1t8URWbuGJSstw-cM"
```


```typescript
await client.api.authentication.githubOAuthRedirect({
  intent: "login",
  code_challenge: "E9Melhoa2OwvFrEMTJguCHaoeK1t8URWbuGJSstw-cM"
});
```


```text
HTTP/1.1 302 Found
Location: https://github.com/login/oauth/authorize?...
```



### `GET /api/v1/auth/github/callback`

Handles the GitHub OAuth callback. Browser-only endpoint.

#### Parameters

| Name | In | Type | Required | Description |
|------|-----|------|----------|-------------|
| `code` | query | string | Yes | OAuth code returned by GitHub |
| `state` | query | string | Yes | State value for CSRF protection |



```bash
curl -L "https://api.hoody.com/api/v1/auth/github/callback?code=acf4d2e9&state=xyz123"
```


```typescript
await client.api.authentication.githubOAuthCallback({
  code: "acf4d2e9",
  state: "xyz123"
});
```


```text
HTTP/1.1 302 Found
Location: https://app.hoody.com/oauth/complete?...
```



### `GET /api/v1/auth/google`

Redirects the browser to Google for OAuth authentication. Browser-only endpoint.

#### Parameters

| Name | In | Type | Required | Description |
|------|-----|------|----------|-------------|
| `redirect_uri` | query | string | No | Frontend URL to redirect to after OAuth completes |
| `code_challenge` | query | string | Yes | PKCE `code_challenge` (base64url SHA-256 of `code_verifier`). Required — all OAuth flows must use PKCE post-migration. |



```bash
curl -L "https://api.hoody.com/api/v1/auth/google?code_challenge=E9Melhoa2OwvFrEMTJguCHaoeK1t8URWbuGJSstw-cM"
```


```typescript
await client.api.authentication.googleOAuthRedirect({
  code_challenge: "E9Melhoa2OwvFrEMTJguCHaoeK1t8URWbuGJSstw-cM"
});
```


```text
HTTP/1.1 302 Found
Location: https://accounts.google.com/o/oauth2/v2/auth?...
```



### `GET /api/v1/auth/google/callback`

Handles the Google OAuth callback. Browser-only endpoint.

#### Parameters

| Name | In | Type | Required | Description |
|------|-----|------|----------|-------------|
| `code` | query | string | Yes | OAuth code returned by Google |
| `state` | query | string | Yes | State value for CSRF protection |



```bash
curl -L "https://api.hoody.com/api/v1/auth/google/callback?code=4/0AY0e-g7X&state=xyz123"
```


```typescript
await client.api.authentication.googleOAuthCallback({
  code: "4/0AY0e-g7X",
  state: "xyz123"
});
```


```text
HTTP/1.1 302 Found
Location: https://app.hoody.com/oauth/complete?...
```



### `POST /api/v1/auth/launch/initiate`

Issues a one-shot launch ticket bound to the request `Origin` header. The frontend navigates the popup to the returned `launch_url`, which consumes the ticket and runs the existing PKCE-protected OAuth flow with `state_id` and `opener_origin` plumbed through.

#### Request Body

| Name | Type | Required | Description |
|------|------|----------|-------------|
| `provider` | string | Yes | OAuth provider. Allowed values: `github`, `google`. |
| `code_challenge` | string | Yes | PKCE `code_challenge` (base64url SHA-256 of `code_verifier`, 43–128 chars) |
| `state_id` | string | Yes | Per-attempt UUID v4 — plumbed through state JWT, cookie name, fragment, message filter |



```bash
curl -X POST https://api.hoody.com/api/v1/auth/launch/initiate \
  -H "Content-Type: application/json" \
  -d '{
    "provider": "github",
    "code_challenge": "E9Melhoa2OwvFrEMTJguCHaoeK1t8URWbuGJSstw-cM",
    "state_id": "f7a3b1c9-4d2e-4a8b-9f0c-1e2d3a4b5c6d"
  }'
```


```typescript
const { data } = await client.api.authentication.oauthLaunchInitiate({
  provider: "github",
  code_challenge: "E9Melhoa2OwvFrEMTJguCHaoeK1t8URWbuGJSstw-cM",
  state_id: "f7a3b1c9-4d2e-4a8b-9f0c-1e2d3a4b5c6d"
});
```


```json
{
  "statusCode": 200,
  "data": {
    "launch_url": "https://api.hoody.com/api/v1/auth/launch/start?ticket=tkt_8d7f6e5c4b3a2918"
  }
}
```



### `GET /api/v1/auth/launch/start`

GET endpoint the popup navigates to. Consumes the launch ticket atomically and runs the existing OAuth redirect flow. Sets `Referrer-Policy: no-referrer`.

#### Parameters

| Name | In | Type | Required | Description |
|------|-----|------|----------|-------------|
| `ticket` | query | string | Yes | One-shot ticket from `/launch/initiate` response |



```bash
curl -L "https://api.hoody.com/api/v1/auth/launch/start?ticket=tkt_8d7f6e5c4b3a2918"
```


```typescript
await client.api.authentication.oauthLaunchStart({
  ticket: "tkt_8d7f6e5c4b3a2918"
});
```


```text
HTTP/1.1 302 Found
Location: https://github.com/login/oauth/authorize?...
```


```json
{
  "statusCode": 410,
  "error": "Gone",
  "message": "This launch ticket has already been used or has expired."
}
```



### `POST /api/v1/auth/intent/cancel`

Cancel a pending OAuth AuthIntent or 2FA `temp_token`. Idempotent. Used by the handoff page when the user dismisses the confirmation. Send the token as `Authorization: Bearer &lt;intent or temp_token&gt;`.



```bash
curl -X POST https://api.hoody.com/api/v1/auth/intent/cancel \
  -H "Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9..."
```


```typescript
await client.api.authentication.oauthCancelIntent();
```


```text
HTTP/1.1 204 No Content
```



## Account

### `POST /api/v1/auth/signup`

Create a new account with email and password. A verification email is sent. The account is not active until the email is verified.

#### Request Body

| Name | Type | Required | Description |
|------|------|----------|-------------|
| `email` | string | Yes | Email address for the new account |
| `password` | string | Yes | Password (min 12 chars, must include uppercase, lowercase, number, and special char) |
| `region` | string | No | Optional preferred server region (e.g. `eu-west`). If omitted, auto-assigned by GeoIP proximity. |



```bash
curl -X POST https://api.hoody.com/api/v1/auth/signup \
  -H "Content-Type: application/json" \
  -d '{
    "email": "john.doe@example.com",
    "password": "SecurePassword123!"
  }'
```


```typescript
const { data } = await client.api.authentication.signup({
  email: "john.doe@example.com",
  password: "SecurePassword123!"
});
```


```json
{
  "statusCode": 200,
  "message": "Account created. Please check your email to verify your address.",
  "data": {
    "email": "john.doe@example.com"
  }
}
```


```json
{
  "statusCode": 400,
  "message": "Password must be at least 12 characters and include uppercase, lowercase, number, and special character."
}
```


```json
{
  "statusCode": 403,
  "message": "Signups are currently disabled"
}
```



### `POST /api/v1/users/auth/login`

Authenticate with username and password to receive a JWT access token (expires in 1 day) and refresh token (expires in 7 days). Use the access token in the `Authorization` header for subsequent requests: `Authorization: Bearer {token}`.

#### Request Body

| Name | Type | Required | Description |
|------|------|----------|-------------|
| `username` | string | No | Username (alphanumeric, underscores, hyphens). Provide `username` or `email`. |
| `email` | string | No | Email address (alternative to `username`) |
| `password` | string | Yes | Account password. Must be at least 8 characters with uppercase, lowercase, and number. |
| `response_mode` | string | No | Response shape. `tokens` (default) returns access/refresh tokens. `intent` returns an opaque `auth_intent_token` for PKCE exchange. Allowed values: `intent`, `tokens`. |
| `code_challenge` | string | No | PKCE `code_challenge` (base64url SHA-256 of `code_verifier`). Required when `response_mode=intent`. |



```bash
curl -X POST https://api.hoody.com/api/v1/users/auth/login \
  -H "Content-Type: application/json" \
  -d '{
    "username": "john_doe",
    "password": "SecurePassword123!"
  }'
```


```typescript
const { data } = await client.api.authentication.login({
  username: "john_doe",
  password: "SecurePassword123!"
});
```


```json
{
  "statusCode": 200,
  "message": "Login successful",
  "data": {
    "token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...",
    "refreshToken": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...",
    "expires_at": "2025-11-28T20:19:00.000Z",
    "expires_in": 86400,
    "refresh_expires_at": "2025-12-04T20:19:00.000Z",
    "refresh_expires_in": 604800,
    "client_ip": "192.168.1.100",
    "recent_login_ips": [
      {
        "ip": "192.168.1.100",
        "timestamp": "2025-01-15T10:30:00.000Z"
      },
      {
        "ip": "10.0.0.5",
        "timestamp": "2025-01-14T09:15:00.000Z"
      }
    ],
    "auth_token_count": 2,
    "user": {
      "id": "507f1f77bcf86cd799439011",
      "username": "john_doe",
      "alias": "John Doe",
      "email": "john.doe@example.com",
      "is_admin": false,
      "is_banned": false,
      "metadata": {},
      "created_at": "2024-12-01T10:00:00.000Z",
      "updated_at": "2025-01-15T10:30:00.000Z"
    }
  }
}
```

The response is signed via `X-Hoody-Signature`. When the user has 2FA enabled, the response includes `data.requires_2fa`, `data.temp_token`, and `data.method: "totp"` instead of the token pair.


```json
{
  "statusCode": 400,
  "error": "Bad Request",
  "message": "Validation failed"
}
```

| Error Code | Title | Description | Resolution |
|------------|-------|-------------|------------|
| `VALIDATION_ERROR` | Invalid input parameters | One or more request parameters failed validation | Check the error message for specific field requirements and correct your input |
| `MISSING_REQUIRED_FIELD` | Required field missing | One or more required fields are missing from the request | Include all required fields as specified in the API documentation |


```json
{
  "statusCode": 401,
  "error": "Unauthorized",
  "message": "Invalid credentials"
}
```

| Error Code | Title | Description | Resolution |
|------------|-------|-------------|------------|
| `INVALID_TOKEN` | Invalid authentication token | The provided authentication token is malformed or invalid | Obtain a new token by logging in again or using a valid auth token |
| `TOKEN_EXPIRED` | Authentication token expired | The provided authentication token has expired | Obtain a new token by logging in again or refreshing your session |
| `EMAIL_NOT_VERIFIED` | Email not verified | Returned by the login endpoint when the password is correct but the account's email address has not been verified yet. Reachable only after bcrypt confirms the password, so it is not an enumeration oracle. Response carries data.email so the client can offer a 'resend verification email' CTA without re-prompting. | Complete email verification by clicking the link sent to your inbox, or call /auth/resend-verification to receive a new link, or complete a password reset which also implicitly verifies the email. |



### `POST /api/v1/users/auth/logout`

Log out the current user. Creates an audit log entry. In a stateless JWT setup, the client should also discard the token. This endpoint works even for banned users.



```bash
curl -X POST https://api.hoody.com/api/v1/users/auth/logout \
  -H "Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9..."
```


```typescript
await client.api.authentication.logout();
```


```json
{
  "statusCode": 200,
  "message": "Logout successful"
}
```


```json
{
  "statusCode": 401,
  "error": "Unauthorized",
  "message": "Authentication token required"
}
```

| Error Code | Title | Description | Resolution |
|------------|-------|-------------|------------|
| `MISSING_TOKEN` | Authentication token missing | No authentication token was provided in the request | Include a valid JWT token in the `Authorization` header as `Bearer &lt;token&gt;` |
| `INVALID_TOKEN` | Invalid authentication token | The provided authentication token is malformed or invalid | Obtain a new token by logging in again or using a valid auth token |
| `TOKEN_EXPIRED` | Authentication token expired | The provided authentication token has expired | Obtain a new token by logging in again or refreshing your session |



### `POST /api/v1/users/auth/refresh`

Exchange a valid refresh token for a new access token and new refresh token. Send the refresh token in the `Authorization` header: `Authorization: Bearer {refreshToken}`.

#### Request Body

| Name | Type | Required | Description |
|------|------|----------|-------------|
| `refreshToken` | string | Yes | Valid refresh token from previous login/refresh |



```bash
curl -X POST https://api.hoody.com/api/v1/users/auth/refresh \
  -H "Content-Type: application/json" \
  -d '{
    "refreshToken": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9..."
  }'
```


```typescript
const { data } = await client.api.authentication.refreshToken({
  refreshToken: "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9..."
});
```


```json
{
  "statusCode": 200,
  "message": "Token refreshed successfully",
  "data": {
    "token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...",
    "refreshToken": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...",
    "expires_at": "2025-11-28T20:19:00.000Z",
    "expires_in": 86400,
    "refresh_expires_at": "2025-12-04T20:19:00.000Z",
    "refresh_expires_in": 604800
  }
}
```


```json
{
  "statusCode": 401,
  "error": "Unauthorized",
  "message": "Invalid or expired refresh token"
}
```

| Error Code | Title | Description | Resolution |
|------------|-------|-------------|------------|
| `MISSING_TOKEN` | Authentication token missing | No authentication token was provided in the request | Include a valid JWT token in the `Authorization` header as `Bearer &lt;token&gt;` |
| `INVALID_TOKEN` | Invalid authentication token | The provided authentication token is malformed or invalid | Obtain a new token by logging in again or using a valid auth token |
| `TOKEN_EXPIRED` | Authentication token expired | The provided authentication token has expired | Obtain a new token by logging in again or refreshing your session |



### `GET /api/v1/users/auth/me`

Retrieve the profile of the currently authenticated user. Works with JWT, auth token, or Basic authentication. When authenticated with an auth token, the response includes `data.auth_token` introspection details (permissions and realm restrictions). This endpoint works even for banned users (read-only access).



```bash
curl https://api.hoody.com/api/v1/users/auth/me \
  -H "Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9..."
```


```typescript
const { data } = await client.api.authentication.getCurrentUser();
```


```json
{
  "statusCode": 200,
  "message": "Current user retrieved successfully",
  "data": {
    "id": "507f1f77bcf86cd799439011",
    "username": "john_doe",
    "alias": "John Doe",
    "email": "john.doe@example.com",
    "is_admin": false,
    "is_banned": false,
    "email_verified": true,
    "metadata": {},
    "created_at": "2024-12-01T10:00:00.000Z",
    "updated_at": "2025-01-15T10:30:00.000Z",
    "pending_pool_invitations": 0
  }
}
```


```json
{
  "statusCode": 401,
  "error": "Unauthorized",
  "message": "Authentication token required"
}
```

| Error Code | Title | Description | Resolution |
|------------|-------|-------------|------------|
| `MISSING_TOKEN` | Authentication token missing | No authentication token was provided in the request | Include a valid JWT token in the `Authorization` header as `Bearer &lt;token&gt;` |
| `INVALID_TOKEN` | Invalid authentication token | The provided authentication token is malformed or invalid | Obtain a new token by logging in again or using a valid auth token |
| `TOKEN_EXPIRED` | Authentication token expired | The provided authentication token has expired | Obtain a new token by logging in again or refreshing your session |



## Email verification & password recovery

### `POST /api/v1/auth/verify-email`

Verify the email address using the token from the verification email. The default response returns full login credentials. When `response_mode=intent` + `code_challenge` are provided, returns an opaque `auth_intent_token` for PKCE exchange (hosted auth UI flow). If 2FA is enabled on the account, returns `requires_2fa` + `temp_token` instead.

#### Request Body

| Name | Type | Required | Description |
|------|------|----------|-------------|
| `token` | string | Yes | Verification token from the email link (64 characters) |
| `response_mode` | string | No | Response shape. `tokens` (default) returns access/refresh tokens. `intent` returns an opaque `auth_intent_token` for PKCE exchange. Allowed values: `intent`, `tokens`. |
| `code_challenge` | string | No | PKCE `code_challenge` (base64url SHA-256 of `code_verifier`). Required when `response_mode=intent`. |



```bash
curl -X POST https://api.hoody.com/api/v1/auth/verify-email \
  -H "Content-Type: application/json" \
  -d '{
    "token": "a1b2c3d4e5f6789012345678901234567890abcdefabcdefabcdefabcdef1234"
  }'
```


```typescript
const { data } = await client.api.authentication.verifyEmail({
  token: "a1b2c3d4e5f6789012345678901234567890abcdefabcdefabcdefabcdef1234"
});
```


```json
{
  "statusCode": 200,
  "message": "Email verified. Login successful.",
  "data": {
    "token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...",
    "refreshToken": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...",
    "expires_at": "2025-11-28T20:19:00.000Z",
    "expires_in": 86400,
    "refresh_expires_at": "2025-12-04T20:19:00.000Z",
    "refresh_expires_in": 604800,
    "user": {
      "id": "507f1f77bcf86cd799439011",
      "username": "john_doe",
      "alias": "John Doe",
      "email": "john.doe@example.com",
      "is_admin": false,
      "email_verified": true,
      "signup_method": "email",
      "avatar_url": null,
      "created_at": "2024-12-01T10:00:00.000Z",
      "updated_at": "2025-01-15T10:30:00.000Z"
    }
  }
}
```


```json
{
  "statusCode": 400,
  "message": "Invalid or expired verification token"
}
```



### `POST /api/v1/auth/resend-verification`

Resend the email verification link. Always returns success to prevent email enumeration.

#### Request Body

| Name | Type | Required | Description |
|------|------|----------|-------------|
| `email` | string | Yes | Email address to resend verification to |



```bash
curl -X POST https://api.hoody.com/api/v1/auth/resend-verification \
  -H "Content-Type: application/json" \
  -d '{
    "email": "john.doe@example.com"
  }'
```


```typescript
await client.api.authentication.resendVerification({
  email: "john.doe@example.com"
});
```


```json
{
  "statusCode": 200,
  "message": "If an account exists for that email and is not yet verified, a verification link has been sent."
}
```



### `POST /api/v1/auth/forgot-password`

Send a password reset email. Always returns success to prevent email enumeration.

#### Request Body

| Name | Type | Required | Description |
|------|------|----------|-------------|
| `email` | string | Yes | Email address associated with the account |



```bash
curl -X POST https://api.hoody.com/api/v1/auth/forgot-password \
  -H "Content-Type: application/json" \
  -d '{
    "email": "john.doe@example.com"
  }'
```


```typescript
await client.api.authentication.forgotPassword({
  email: "john.doe@example.com"
});
```


```json
{
  "statusCode": 200,
  "message": "If an account exists for that email, a password reset link has been sent."
}
```



### `POST /api/v1/auth/reset-password`

Set a new password using the reset token from the password reset email.

#### Request Body

| Name | Type | Required | Description |
|------|------|----------|-------------|
| `token` | string | Yes | Password reset token from the email link (64 characters) |
| `password` | string | Yes | New password (min 12 chars) |



```bash
curl -X POST https://api.hoody.com/api/v1/auth/reset-password \
  -H "Content-Type: application/json" \
  -d '{
    "token": "a1b2c3d4e5f6789012345678901234567890abcdefabcdefabcdefabcdef1234",
    "password": "NewSecurePassword123!"
  }'
```


```typescript
await client.api.authentication.resetPassword({
  token: "a1b2c3d4e5f6789012345678901234567890abcdefabcdefabcdefabcdef1234",
  password: "NewSecurePassword123!"
});
```


```json
{
  "statusCode": 200,
  "message": "Password reset successful. You can now log in with your new password."
}
```


```json
{
  "statusCode": 400,
  "message": "Invalid or expired reset token"
}
```



## Two-factor authentication

### `GET /api/v1/users/auth/2fa/status`

Check the current 2FA status for the authenticated user, including whether it is enabled and how many backup codes remain.



```bash
curl https://api.hoody.com/api/v1/users/auth/2fa/status \
  -H "Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9..."
```


```typescript
const { data } = await client.api.tfa.getStatus();
```


```json
{
  "statusCode": 200,
  "message": "2FA status retrieved",
  "data": {
    "enabled": true,
    "verified": true,
    "enabled_at": "2025-01-14T21:00:00.000Z",
    "backup_codes_remaining": 8,
    "require_for_tokens": true
  }
}
```


```json
{
  "statusCode": 401,
  "error": "Unauthorized",
  "message": "Authentication token required"
}
```

| Error Code | Title | Description | Resolution |
|------------|-------|-------------|------------|
| `MISSING_TOKEN` | Authentication token missing | No authentication token was provided in the request | Include a valid JWT token in the `Authorization` header as `Bearer &lt;token&gt;` |
| `INVALID_TOKEN` | Invalid authentication token | The provided authentication token is malformed or invalid | Obtain a new token by logging in again or using a valid auth token |
| `TOKEN_EXPIRED` | Authentication token expired | The provided authentication token has expired | Obtain a new token by logging in again or refreshing your session |
| `INVALID_OTP_CODE` | Invalid OTP code | The provided 2FA code is incorrect or has expired | Generate a new code from your authenticator app and try again |


```json
{
  "statusCode": 429,
  "error": "Too Many Requests",
  "message": "Too many failed attempts. Account locked for 15 minutes.",
  "data": {
    "lockout_seconds": 900
  }
}
```

| Error Code | Title | Description | Resolution |
|------------|-------|-------------|------------|
| `TWOFACTOR_RATE_LIMIT` | 2FA verification locked | Too many failed 2FA verification attempts. Account is temporarily locked. | Wait for the lockout period to expire (15 minutes) before trying again |



### `POST /api/v1/users/auth/2fa/setup`

Begin 2FA setup. Requires the current password for verification. Returns a QR code for the authenticator app and backup codes. Save backup codes securely — they are shown only once.

#### Request Body

| Name | Type | Required | Description |
|------|------|----------|-------------|
| `password` | string | Yes | Current account password for verification (8–128 characters) |



```bash
curl -X POST https://api.hoody.com/api/v1/users/auth/2fa/setup \
  -H "Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9..." \
  -H "Content-Type: application/json" \
  -d '{
    "password": "SecurePassword123!"
  }'
```


```typescript
const { data } = await client.api.tfa.setup({
  password: "SecurePassword123!"
});
```


```json
{
  "statusCode": 200,
  "message": "2FA setup initiated",
  "data": {
    "qr_code": "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAA...",
    "manual_entry_key": "JBSWY3DPEHPK3PXP",
    "backup_codes": [
      "a1b2c3d4e5",
      "f6g7h8i9j0",
      "k1l2m3n4o5",
      "p6q7r8s9t0",
      "u1v2w3x4y5",
      "z6a7b8c9d0",
      "e1f2g3h4i5",
      "j6k7l8m9n0",
      "o1p2q3r4s5",
      "t6u7v8w9x0"
    ]
  }
}
```


```json
{
  "statusCode": 400,
  "error": "Bad Request",
  "message": "Incorrect password"
}
```

| Error Code | Title | Description | Resolution |
|------------|-------|-------------|------------|
| `VALIDATION_ERROR` | Invalid input parameters | One or more request parameters failed validation | Check the error message for specific field requirements and correct your input |
| `MISSING_REQUIRED_FIELD` | Required field missing | One or more required fields are missing from the request | Include all required fields as specified in the API documentation |
| `INCORRECT_PASSWORD` | Incorrect password | The provided password does not match the account password | Verify your password and try again |
| `TWOFACTOR_ALREADY_ENABLED` | 2FA already enabled | Two-factor authentication is already enabled for this account | Disable 2FA first if you want to set it up again |


```json
{
  "statusCode": 401,
  "error": "Unauthorized",
  "message": "Authentication token required"
}
```

| Error Code | Title | Description | Resolution |
|------------|-------|-------------|------------|
| `MISSING_TOKEN` | Authentication token missing | No authentication token was provided in the request | Include a valid JWT token in the `Authorization` header as `Bearer &lt;token&gt;` |
| `INVALID_TOKEN` | Invalid authentication token | The provided authentication token is malformed or invalid | Obtain a new token by logging in again or using a valid auth token |
| `TOKEN_EXPIRED` | Authentication token expired | The provided authentication token has expired | Obtain a new token by logging in again or refreshing your session |



### `POST /api/v1/users/auth/2fa/verify-setup`

Verify and complete 2FA setup by providing the first code from the authenticator app. This confirms the setup is working correctly.

#### Request Body

| Name | Type | Required | Description |
|------|------|----------|-------------|
| `code` | string | Yes | 6-digit code from the authenticator app |



```bash
curl -X POST https://api.hoody.com/api/v1/users/auth/2fa/verify-setup \
  -H "Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9..." \
  -H "Content-Type: application/json" \
  -d '{
    "code": "482915"
  }'
```


```typescript
const { data } = await client.api.tfa.verifySetup({
  code: "482915"
});
```


```json
{
  "statusCode": 200,
  "message": "2FA successfully enabled",
  "data": {
    "enabled": true,
    "enabled_at": "2025-01-14T21:00:00.000Z"
  }
}
```


```json
{
  "statusCode": 400,
  "error": "Bad Request",
  "message": "Invalid OTP code"
}
```

| Error Code | Title | Description | Resolution |
|------------|-------|-------------|------------|
| `VALIDATION_ERROR` | Invalid input parameters | One or more request parameters failed validation | Check the error message for specific field requirements and correct your input |
| `MISSING_REQUIRED_FIELD` | Required field missing | One or more required fields are missing from the request | Include all required fields as specified in the API documentation |
| `INVALID_OTP_CODE` | Invalid OTP code | The provided 2FA code is incorrect or has expired | Generate a new code from your authenticator app and try again |
| `TWOFACTOR_NOT_VERIFIED` | 2FA setup not verified | 2FA setup was initiated but not yet verified | Complete the setup by verifying your first code |


```json
{
  "statusCode": 401,
  "error": "Unauthorized",
  "message": "Invalid or expired 2FA code",
  "data": {
    "attempts_remaining": 4
  }
}
```

| Error Code | Title | Description | Resolution |
|------------|-------|-------------|------------|
| `MISSING_TOKEN` | Authentication token missing | No authentication token was provided in the request | Include a valid JWT token in the `Authorization` header as `Bearer &lt;token&gt;` |
| `INVALID_TOKEN` | Invalid authentication token | The provided authentication token is malformed or invalid | Obtain a new token by logging in again or using a valid auth token |
| `TOKEN_EXPIRED` | Authentication token expired | The provided authentication token has expired | Obtain a new token by logging in again or refreshing your session |
| `INVALID_OTP_CODE` | Invalid OTP code | The provided 2FA code is incorrect or has expired | Generate a new code from your authenticator app and try again |


```json
{
  "statusCode": 429,
  "error": "Too Many Requests",
  "message": "Too many failed attempts. Account locked for 15 minutes.",
  "data": {
    "lockout_seconds": 900
  }
}
```

| Error Code | Title | Description | Resolution |
|------------|-------|-------------|------------|
| `TWOFACTOR_RATE_LIMIT` | 2FA verification locked | Too many failed 2FA verification attempts. Account is temporarily locked. | Wait for the lockout period to expire (15 minutes) before trying again |



### `POST /api/v1/users/auth/2fa/verify`

Complete login by verifying the 2FA code. Use the `temp_token` from the login response and provide either a 6-digit OTP code or a backup code.

#### Request Body

| Name | Type | Required | Description |
|------|------|----------|-------------|
| `temp_token` | string | No | Temporary token from login response (valid for 5 minutes). Alternatively pass it as `Authorization: Bearer` header. |
| `code` | string | Yes | 6-digit OTP code from the authenticator app OR 10-character backup code |
| `response_mode` | string | No | Response shape. `tokens` (default) returns access/refresh tokens. `intent` returns an opaque `auth_intent_token` for PKCE exchange. Allowed values: `intent`, `tokens`. |



```bash
curl -X POST https://api.hoody.com/api/v1/users/auth/2fa/verify \
  -H "Content-Type: application/json" \
  -d '{
    "temp_token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...",
    "code": "482915"
  }'
```


```typescript
const { data } = await client.api.tfa.verify({
  temp_token: "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...",
  code: "482915"
});
```


```json
{
  "statusCode": 200,
  "message": "Authentication successful",
  "data": {
    "token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...",
    "refreshToken": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...",
    "user": {
      "id": "507f1f77bcf86cd799439011",
      "alias": "John Doe"
    }
  }
}
```


```json
{
  "statusCode": 400,
  "error": "Bad Request",
  "message": "Invalid or expired 2FA code"
}
```

| Error Code | Title | Description | Resolution |
|------------|-------|-------------|------------|
| `VALIDATION_ERROR` | Invalid input parameters | One or more request parameters failed validation | Check the error message for specific field requirements and correct your input |
| `MISSING_REQUIRED_FIELD` | Required field missing | One or more required fields are missing from the request | Include all required fields as specified in the API documentation |
| `INVALID_OTP_CODE` | Invalid OTP code | The provided 2FA code is incorrect or has expired | Generate a new code from your authenticator app and try again |
| `INVALID_BACKUP_CODE` | Invalid backup code | The provided backup code is incorrect or has already been used | Verify the backup code is correct and has not been used previously |
| `INVALID_TEMP_TOKEN` | Invalid temporary token | The temporary token from login has expired or is invalid | Log in again to get a new temporary token |


```json
{
  "statusCode": 401,
  "error": "Unauthorized",
  "message": "Invalid or expired 2FA code",
  "data": {
    "attempts_remaining": 4
  }
}
```

| Error Code | Title | Description | Resolution |
|------------|-------|-------------|------------|
| `INVALID_OTP_CODE` | Invalid OTP code | The provided 2FA code is incorrect or has expired | Generate a new code from your authenticator app and try again |
| `INVALID_BACKUP_CODE` | Invalid backup code | The provided backup code is incorrect or has already been used | Verify the backup code is correct and has not been used previously |


```json
{
  "statusCode": 429,
  "error": "Too Many Requests",
  "message": "Too many failed attempts. Account locked for 15 minutes.",
  "data": {
    "lockout_seconds": 900
  }
}
```

| Error Code | Title | Description | Resolution |
|------------|-------|-------------|------------|
| `TWOFACTOR_RATE_LIMIT` | 2FA verification locked | Too many failed 2FA verification attempts. Account is temporarily locked. | Wait for the lockout period to expire (15 minutes) before trying again |



### `POST /api/v1/users/auth/2fa/backup-codes/regenerate`

Generate new backup codes (invalidates all existing ones). Requires password and current OTP code for security. Save the new codes securely.

#### Request Body

| Name | Type | Required | Description |
|------|------|----------|-------------|
| `password` | string | Yes | Current account password (8–128 characters) |
| `code` | string | Yes | 6-digit OTP code from the authenticator app |



```bash
curl -X POST https://api.hoody.com/api/v1/users/auth/2fa/backup-codes/regenerate \
  -H "Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9..." \
  -H "Content-Type: application/json" \
  -d '{
    "password": "SecurePassword123!",
    "code": "482915"
  }'
```


```typescript
const { data } = await client.api.tfa.regenerateBackupCodes({
  password: "SecurePassword123!",
  code: "482915"
});
```


```json
{
  "statusCode": 200,
  "message": "Backup codes regenerated",
  "data": {
    "backup_codes": [
      "a1b2c3d4e5",
      "f6g7h8i9j0",
      "k1l2m3n4o5",
      "p6q7r8s9t0",
      "u1v2w3x4y5",
      "z6a7b8c9d0",
      "e1f2g3h4i5",
      "j6k7l8m9n0",
      "o1p2q3r4s5",
      "t6u7v8w9x0"
    ]
  }
}
```


```json
{
  "statusCode": 400,
  "error": "Bad Request",
  "message": "Incorrect password"
}
```

| Error Code | Title | Description | Resolution |
|------------|-------|-------------|------------|
| `VALIDATION_ERROR` | Invalid input parameters | One or more request parameters failed validation | Check the error message for specific field requirements and correct your input |
| `MISSING_REQUIRED_FIELD` | Required field missing | One or more required fields are missing from the request | Include all required fields as specified in the API documentation |
| `INCORRECT_PASSWORD` | Incorrect password | The provided password does not match the account password | Verify your password and try again |
| `INVALID_OTP_CODE` | Invalid OTP code | The provided 2FA code is incorrect or has expired | Generate a new code from your authenticator app and try again |
| `TWOFACTOR_NOT_ENABLED` | 2FA not enabled | Two-factor authentication is not enabled for this account | Set up 2FA first using the setup endpoint |


```json
{
  "statusCode": 401,
  "error": "Unauthorized",
  "message": "Invalid or expired 2FA code",
  "data": {
    "attempts_remaining": 4
  }
}
```

| Error Code | Title | Description | Resolution |
|------------|-------|-------------|------------|
| `MISSING_TOKEN` | Authentication token missing | No authentication token was provided in the request | Include a valid JWT token in the `Authorization` header as `Bearer &lt;token&gt;` |
| `INVALID_TOKEN` | Invalid authentication token | The provided authentication token is malformed or invalid | Obtain a new token by logging in again or using a valid auth token |
| `TOKEN_EXPIRED` | Authentication token expired | The provided authentication token has expired | Obtain a new token by logging in again or refreshing your session |
| `INVALID_OTP_CODE` | Invalid OTP code | The provided 2FA code is incorrect or has expired | Generate a new code from your authenticator app and try again |


```json
{
  "statusCode": 429,
  "error": "Too Many Requests",
  "message": "Too many failed attempts. Account locked for 15 minutes.",
  "data": {
    "lockout_seconds": 900
  }
}
```

| Error Code | Title | Description | Resolution |
|------------|-------|-------------|------------|
| `TWOFACTOR_RATE_LIMIT` | 2FA verification locked | Too many failed 2FA verification attempts. Account is temporarily locked. | Wait for the lockout period to expire (15 minutes) before trying again |



### `PATCH /api/v1/users/auth/2fa/token-gate`

Enable or disable the OTP requirement for token mutation operations. Disabling requires both password and OTP.

#### Request Body

| Name | Type | Required | Description |
|------|------|----------|-------------|
| `enabled` | boolean | Yes | `true` = require OTP for token mutations (default), `false` = skip OTP gate |
| `password` | string | No | Required when setting `enabled=false` (security downgrade requires primary-factor reauth) |
| `otp_code` | string | No | TOTP code or backup code. Required when setting `enabled=false`. |



```bash
curl -X PATCH https://api.hoody.com/api/v1/users/auth/2fa/token-gate \
  -H "Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9..." \
  -H "Content-Type: application/json" \
  -d '{
    "enabled": false,
    "password": "SecurePassword123!",
    "otp_code": "482915"
  }'
```


```typescript
const { data } = await client.api.tfa.setTokenGate({
  enabled: false,
  password: "SecurePassword123!",
  otp_code: "482915"
});
```


```json
{
  "statusCode": 200,
  "message": "Token gate updated",
  "data": {
    "require_for_tokens": false
  }
}
```


```json
{
  "statusCode": 400,
  "error": "Bad Request",
  "message": "2FA verification required for this operation"
}
```

| Error Code | Title | Description | Resolution |
|------------|-------|-------------|------------|
| `OTP_REQUIRED` | 2FA verification required | This operation requires 2FA verification because your account has 2FA enabled | Provide an `otp_code` field with a valid TOTP code or backup code |
| `TWOFACTOR_NOT_ENABLED` | 2FA not enabled | Two-factor authentication is not enabled for this account | Set up 2FA first using the setup endpoint |


```json
{
  "statusCode": 401,
  "error": "Unauthorized",
  "message": "Invalid or expired 2FA code"
}
```

| Error Code | Title | Description | Resolution |
|------------|-------|-------------|------------|
| `INVALID_OTP_CODE` | Invalid OTP code | The provided 2FA code is incorrect or has expired | Generate a new code from your authenticator app and try again |
| `INCORRECT_PASSWORD` | Incorrect password | The provided password does not match the account password | Verify your password and try again |


```json
{
  "statusCode": 429,
  "error": "Too Many Requests",
  "message": "Too many failed attempts. Account locked for 15 minutes.",
  "data": {
    "lockout_seconds": 900
  }
}
```

| Error Code | Title | Description | Resolution |
|------------|-------|-------------|------------|
| `TWOFACTOR_RATE_LIMIT` | 2FA verification locked | Too many failed 2FA verification attempts. Account is temporarily locked. | Wait for the lockout period to expire (15 minutes) before trying again |



### `DELETE /api/v1/users/auth/2fa`

Disable 2FA for the account. Requires both the current password and a valid OTP code (or backup code) for security.

#### Request Body

| Name | Type | Required | Description |
|------|------|----------|-------------|
| `password` | string | Yes | Current account password (8–128 characters) |
| `code` | string | Yes | 6-digit OTP code from the authenticator app OR backup code |



```bash
curl -X DELETE https://api.hoody.com/api/v1/users/auth/2fa \
  -H "Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9..." \
  -H "Content-Type: application/json" \
  -d '{
    "password": "SecurePassword123!",
    "code": "482915"
  }'
```


```typescript
const { data } = await client.api.tfa.disable({
  password: "SecurePassword123!",
  code: "482915"
});
```


```json
{
  "statusCode": 200,
  "message": "2FA successfully disabled"
}
```


```json
{
  "statusCode": 400,
  "error": "Bad Request",
  "message": "Incorrect password"
}
```

| Error Code | Title | Description | Resolution |
|------------|-------|-------------|------------|
| `VALIDATION_ERROR` | Invalid input parameters | One or more request parameters failed validation | Check the error message for specific field requirements and correct your input |
| `MISSING_REQUIRED_FIELD` | Required field missing | One or more required fields are missing from the request | Include all required fields as specified in the API documentation |
| `INCORRECT_PASSWORD` | Incorrect password | The provided password does not match the account password | Verify your password and try again |
| `INVALID_OTP_CODE` | Invalid OTP code | The provided 2FA code is incorrect or has expired | Generate a new code from your authenticator app and try again |
| `INVALID_BACKUP_CODE` | Invalid backup code | The provided backup code is incorrect or has already been used | Verify the backup code is correct and has not been used previously |
| `TWOFACTOR_NOT_ENABLED` | 2FA not enabled | Two-factor authentication is not enabled for this account | Set up 2FA first using the setup endpoint |


```json
{
  "statusCode": 401,
  "error": "Unauthorized",
  "message": "Invalid or expired 2FA code",
  "data": {
    "attempts_remaining": 4
  }
}
```

| Error Code | Title | Description | Resolution |
|------------|-------|-------------|------------|
| `MISSING_TOKEN` | Authentication token missing | No authentication token was provided in the request | Include a valid JWT token in the `Authorization` header as `Bearer &lt;token&gt;` |
| `INVALID_TOKEN` | Invalid authentication token | The provided authentication token is malformed or invalid | Obtain a new token by logging in again or using a valid auth token |
| `TOKEN_EXPIRED` | Authentication token expired | The provided authentication token has expired | Obtain a new token by logging in again or refreshing your session |
| `INVALID_OTP_CODE` | Invalid OTP code | The provided 2FA code is incorrect or has expired | Generate a new code from your authenticator app and try again |
| `INVALID_BACKUP_CODE` | Invalid backup code | The provided backup code is incorrect or has already been used | Verify the backup code is correct and has not been used previously |


```json
{
  "statusCode": 429,
  "error": "Too Many Requests",
  "message": "Too many failed attempts. Account locked for 15 minutes.",
  "data": {
    "lockout_seconds": 900
  }
}
```

| Error Code | Title | Description | Resolution |
|------------|-------|-------------|------------|
| `TWOFACTOR_RATE_LIMIT` | 2FA verification locked | Too many failed 2FA verification attempts. Account is temporarily locked. | Wait for the lockout period to expire (15 minutes) before trying again |