> ## Documentation Index
> Fetch the complete documentation index at: https://careerk.mintlify.site/llms.txt
> Use this file to discover all available pages before exploring further.

# Login

> Authenticate existing users and receive tokens

## Endpoint

```
POST /api/v1/auth/login
```

**Base URL**: `http://localhost:3000/api/v1`

<Note>This endpoint is **public** and does not require authentication.</Note>

## Request Body

<ParamField body="email" type="string" required>
  User's registered email address (works for both job seekers and companies).
</ParamField>

<ParamField body="password" type="string" required>
  User's password. Minimum 6 characters.
</ParamField>

## Request Shape

```typescript theme={null}
interface LoginDto {
  email: string;
  password: string;
}
```

## Response

### Success Response (200 OK)

#### TypeScript Types

```typescript theme={null}
interface JobSeekerLoginResponse {
  success: boolean;
  data: {
    id: string;
    email: string;
    firstName: string;
    lastName: string;
    profileImageUrl: string | null;
    isActive: boolean;
    isVerified: boolean;
    lastLoginAt: string;
    createdAt: string;
    updatedAt: string;
    role: 'job-seeker';
    accessToken: string;
  };
  message: string;
  meta: {
    timestamp: string;
    path: string;
    method: string;
  };
}

interface CompanyLoginResponse {
  success: boolean;
  data: {
    id: string;
    email: string;
    name: string;
    description: string | null;
    logoUrl: string | null;
    industry: string;
    size: 'SIZE_1_50' | 'SIZE_51_200' | 'SIZE_201_1000' | 'SIZE_1000_PLUS';
    type: 'STARTUP' | 'SCALE_UP' | 'ENTERPRISE' | 'NON_PROFIT' | 'GOVERNMENT';
    headquartersLocation: string | null;
    foundedYear: number | null;
    websiteUrl: string | null;
    benefits: string | null;
    linkedIn: string | null;
    facebook: string | null;
    twitter: string | null;
    isActive: boolean;
    isVerified: boolean;
    createdAt: string;
    updatedAt: string;
    role: 'company';
    accessToken: string;
  };
  message: string;
  meta: {
    timestamp: string;
    path: string;
    method: string;
  };
}
```

#### For Job Seeker

```json theme={null}
{
  "success": true,
  "data": {
    "id": "550e8400-e29b-41d4-a716-446655440000",
    "email": "john.doe@example.com",
    "firstName": "John",
    "lastName": "Doe",
    "profileImageUrl": null,
    "isActive": true,
    "isVerified": true,
    "lastLoginAt": "2024-01-15T10:30:00.000Z",
    "createdAt": "2024-01-15T10:00:00.000Z",
    "updatedAt": "2024-01-15T10:30:00.000Z",
    "role": "job-seeker",
    "accessToken": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiI1NTBlODQwMC1lMjliLTQxZDQtYTcxNi00NDY2NTU0NDAwMDAiLCJlbWFpbCI6ImpvaG4uZG9lQGV4YW1wbGUuY29tIiwidHlwZSI6IkpPQl9TRUVLRVIiLCJ0b2tlblR5cGUiOiJBQ0NFU1MiLCJpYXQiOjE3MDUzMTU4MDAsImV4cCI6MTcwNTMxOTQwMCwiYXVkIjoibG9jYWxob3N0OjMwMDAiLCJpc3MiOiJsb2NhbGhvc3Q6MzAwMCJ9.signature"
  },
  "meta": {
    "timestamp": "2024-01-15T10:30:00.000Z",
    "path": "/auth/login",
    "method": "POST"
  },
  "message": "Login successfully"
}
```

#### For Company

```json theme={null}
{
  "success": true,
  "data": {
    "id": "660f9500-f39c-52e5-b827-557766551111",
    "email": "hr@techcorp.com",
    "name": "TechCorp Inc.",
    "description": null,
    "logoUrl": null,
    "industry": "Technology",
    "size": "SIZE_51_200",
    "type": "STARTUP",
    "headquartersLocation": null,
    "foundedYear": null,
    "websiteUrl": null,
    "benefits": null,
    "linkedIn": null,
    "facebook": null,
    "twitter": null,
    "isActive": true,
    "isVerified": true,
    "createdAt": "2024-01-15T10:00:00.000Z",
    "updatedAt": "2024-01-15T10:30:00.000Z",
    "role": "company",
    "accessToken": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiI2NjBmOTUwMC1mMzljLTUyZTUtYjgyNy01NTc3NjY1NTExMTEiLCJlbWFpbCI6ImhyQHRlY2hjb3JwLmNvbSIsInR5cGUiOiJDT01QQU5ZIiwidG9rZW5UeXBlIjoiQUNDRVNTIiwiaWF0IjoxNzA1MzE1ODAwLCJleHAiOjE3MDUzMTk0MDAsImF1ZCI6ImxvY2FsaG9zdDozMDAwIiwiaXNzIjoibG9jYWxob3N0OjMwMDAifQ.signature"
  },
  "meta": {
    "timestamp": "2024-01-15T10:30:00.000Z",
    "path": "/auth/login",
    "method": "POST"
  },
  "message": "Login successfully"
}
```

<ResponseField name="success" type="boolean">
  Indicates if the login was successful.
</ResponseField>

<ResponseField name="data" type="object">
  Contains the user/company data and access token.

  <Expandable title="Job Seeker Properties">
    <ResponseField name="id" type="string">
      Unique user identifier (UUID).
    </ResponseField>

    <ResponseField name="email" type="string">
      User's email address.
    </ResponseField>

    <ResponseField name="firstName" type="string">
      User's first name.
    </ResponseField>

    <ResponseField name="lastName" type="string">
      User's last name.
    </ResponseField>

    <ResponseField name="profileImageUrl" type="string | null">
      URL to user's profile image.
    </ResponseField>

    <ResponseField name="isActive" type="boolean">
      Whether the account is active.
    </ResponseField>

    <ResponseField name="isVerified" type="boolean">
      Whether the email is verified.
    </ResponseField>

    <ResponseField name="lastLoginAt" type="string">
      ISO 8601 timestamp of last login.
    </ResponseField>

    <ResponseField name="createdAt" type="string">
      ISO 8601 timestamp of account creation.
    </ResponseField>

    <ResponseField name="updatedAt" type="string">
      ISO 8601 timestamp of last update.
    </ResponseField>

    <ResponseField name="role" type="string">
      User role. Always returns "job-seeker".
    </ResponseField>

    <ResponseField name="accessToken" type="string">
      JWT access token for authenticating API requests. Valid for 1 hour.
    </ResponseField>
  </Expandable>

  <Expandable title="Company Properties">
    <ResponseField name="id" type="string">
      Unique company identifier (UUID).
    </ResponseField>

    <ResponseField name="email" type="string">
      Company's email address.
    </ResponseField>

    <ResponseField name="name" type="string">
      Company name.
    </ResponseField>

    <ResponseField name="description" type="string | null">
      Company description.
    </ResponseField>

    <ResponseField name="logoUrl" type="string | null">
      URL to company logo.
    </ResponseField>

    <ResponseField name="industry" type="string">
      Company industry.
    </ResponseField>

    <ResponseField name="size" type="string">
      Company size (SIZE\_1\_50, SIZE\_51\_200, SIZE\_201\_1000, SIZE\_1000\_PLUS).
    </ResponseField>

    <ResponseField name="type" type="string">
      Company type (STARTUP, SCALE\_UP, ENTERPRISE, NON\_PROFIT, GOVERNMENT).
    </ResponseField>

    <ResponseField name="headquartersLocation" type="string | null">
      Company headquarters location.
    </ResponseField>

    <ResponseField name="foundedYear" type="number | null">
      Year the company was founded.
    </ResponseField>

    <ResponseField name="websiteUrl" type="string | null">
      Company website URL.
    </ResponseField>

    <ResponseField name="benefits" type="string | null">
      Company benefits description.
    </ResponseField>

    <ResponseField name="linkedIn" type="string | null">
      LinkedIn profile URL.
    </ResponseField>

    <ResponseField name="facebook" type="string | null">
      Facebook page URL.
    </ResponseField>

    <ResponseField name="twitter" type="string | null">
      Twitter profile URL.
    </ResponseField>

    <ResponseField name="isActive" type="boolean">
      Whether the account is active.
    </ResponseField>

    <ResponseField name="isVerified" type="boolean">
      Whether the email is verified.
    </ResponseField>

    <ResponseField name="createdAt" type="string">
      ISO 8601 timestamp of account creation.
    </ResponseField>

    <ResponseField name="updatedAt" type="string">
      ISO 8601 timestamp of last update.
    </ResponseField>

    <ResponseField name="role" type="string">
      User role. Always returns "company".
    </ResponseField>

    <ResponseField name="accessToken" type="string">
      JWT access token for authenticating API requests. Valid for 1 hour.
    </ResponseField>
  </Expandable>
</ResponseField>

<ResponseField name="message" type="string">
  Human-readable success message.
</ResponseField>

<ResponseField name="meta" type="object">
  Request metadata from global response interceptor.
</ResponseField>

## Important: Refresh Token Cookie

<Warning>
  The refresh token is automatically set as an HTTP-only cookie named `refreshToken`. You must use
  `credentials: 'include'` in your fetch requests to enable cookie handling.
</Warning>

The server sets a cookie with these properties:

* **Name**: `refreshToken`
* **HttpOnly**: true (cannot be accessed via JavaScript)
* **Secure**: true (in production, requires HTTPS)
* **SameSite**: Strict
* **Max-Age**: 24 hours (86400 seconds)

## What Happens on Login

<Steps>
  <Step title="User Lookup">System searches for user in both JobSeeker and Company tables</Step>

  <Step title="Email Verification Check">Verifies that the user's email has been verified</Step>

  <Step title="Password Validation">
    Compares provided password with hashed password using bcrypt
  </Step>

  <Step title="Token Generation">Generates new JWT access and refresh tokens</Step>

  <Step title="Refresh Token Storage">Stores refresh token in Redis and sets HTTP-only cookie</Step>

  <Step title="Response">Returns user data with access token</Step>
</Steps>

## Error Responses

### 400 Bad Request - Validation Error

Returned when request validation fails.

```json theme={null}
{
  "success": false,
  "error": {
    "message": ["email must be an email", "password must be longer than or equal to 6 characters"],
    "statusCode": 400,
    "timestamp": "2026-04-23T20:00:00.000Z",
    "path": "/auth/login",
    "method": "POST",
    "details": "Bad Request"
  }
}
```

### 401 Unauthorized - Invalid Credentials

Returned when email or password is incorrect.

```json theme={null}
{
  "success": false,
  "error": {
    "message": "Invalid credentials",
    "statusCode": 401,
    "timestamp": "2026-04-23T20:00:00.000Z",
    "path": "/auth/login",
    "method": "POST",
    "details": "Unauthorized"
  }
}
```

**Possible causes**:

* Email doesn't exist
* Password is incorrect
* Typo in email or password

### 401 Unauthorized - Email Not Verified

Returned when user hasn't verified their email yet.

```json theme={null}
{
  "success": false,
  "error": {
    "message": "Please verify your email before logging in",
    "statusCode": 401,
    "timestamp": "2026-04-23T20:00:00.000Z",
    "path": "/auth/login",
    "method": "POST",
    "details": "Unauthorized"
  }
}
```

**Solution**: Verify email using the OTP sent during registration with the [Verify Email](/api-reference/auth/verify-email) endpoint.

### 500 Internal Server Error

Returned when an unexpected error occurs.

```json theme={null}
{
  "success": false,
  "error": {
    "message": "Login failed",
    "statusCode": 500,
    "timestamp": "2026-04-23T20:00:00.000Z",
    "path": "/auth/login",
    "method": "POST",
    "details": "Internal Server Error"
  }
}
```

## Validation Rules

| Field    | Rules                      |
| -------- | -------------------------- |
| email    | Must be valid email format |
| password | Minimum 6 characters       |

## Security Notes

<Info>
  **Automatic User Type Detection**: The API automatically detects whether you're logging in as a
  job seeker or company based on the email. You don't need to specify the user type.
</Info>

<Tip>
  **Cookie Credentials**: Always use `credentials: 'include'` in your fetch requests to enable
  cookie handling for the refresh token.
</Tip>

## Response Types

```typescript theme={null}
// Request
interface LoginDto {
  email: string;
  password: string;
}

// Response - Job Seeker
interface JobSeekerLoginResponse {
  success: boolean;
  data: {
    id: string;
    email: string;
    firstName: string;
    lastName: string;
    profileImageUrl: string | null;
    isActive: boolean;
    isVerified: boolean;
    lastLoginAt: string;
    createdAt: string;
    updatedAt: string;
    role: 'job-seeker';
    accessToken: string;
  };
  message: string;
  meta: {
    timestamp: string;
    path: string;
    method: string;
  };
}

// Response - Company
interface CompanyLoginResponse {
  success: boolean;
  data: {
    id: string;
    email: string;
    name: string;
    description: string | null;
    logoUrl: string | null;
    industry: string;
    size: 'SIZE_1_50' | 'SIZE_51_200' | 'SIZE_201_1000' | 'SIZE_1000_PLUS';
    type: 'STARTUP' | 'SCALE_UP' | 'ENTERPRISE' | 'NON_PROFIT' | 'GOVERNMENT';
    headquartersLocation: string | null;
    foundedYear: number | null;
    websiteUrl: string | null;
    benefits: string | null;
    linkedIn: string | null;
    facebook: string | null;
    twitter: string | null;
    isActive: boolean;
    isVerified: boolean;
    createdAt: string;
    role: 'company';
    updatedAt: string;
    accessToken: string;
  };
  message: string;
  meta: {
    timestamp: string;
    path: string;
    method: string;
  };
}
```

## Related Endpoints

* [Register Job Seeker](/api-reference/auth/register-job-seeker) - Create new job seeker account
* [Register Company](/api-reference/auth/register-company) - Create new company account
* [Verify Email](/api-reference/auth/verify-email) - Verify email after registration
* [Refresh Token](/api-reference/auth/refresh-token) - Get new access token when expired
