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

# POST /auth/login — Authenticate your merchant account

> Exchange your email and password for a short-lived access token and a long-lived refresh token. Use the access token to authenticate all subsequent API requests.

This endpoint authenticates your merchant account and returns two tokens: a short-lived **access token** you attach to every API request, and a long-lived **refresh token** you store securely and use only to rotate the access token when it expires. Your account must have a verified email address before login succeeds.

This endpoint applies a strict rate limit.

## Endpoint

```
POST https://api.dubupay.com/api/v1/auth/login
```

No authentication is required.

## Request body

<ParamField body="email" type="string" required>
  The email address registered to your merchant account.
</ParamField>

<ParamField body="password" type="string" required>
  Your account password.
</ParamField>

## Response

HTTP `200 OK` on success.

<ResponseField name="success" type="boolean">
  `true` on a successful login.
</ResponseField>

<ResponseField name="data" type="object">
  <Expandable title="data">
    <ResponseField name="access_token" type="string">
      A signed JWT access token. Valid for **15 minutes**. Include this in the `Authorization: Bearer` header on all authenticated requests.
    </ResponseField>

    <ResponseField name="refresh_token" type="string">
      A signed JWT refresh token. Valid for **7 days**. Use this with `POST /auth/refresh` to get a new access token without re-entering your password. Store it securely — treat it like a password.
    </ResponseField>

    <ResponseField name="merchant" type="object">
      The authenticated merchant's profile.

      <Expandable title="merchant">
        <ResponseField name="id" type="string">
          UUID of the merchant account.
        </ResponseField>

        <ResponseField name="business_name" type="string">
          Registered business name.
        </ResponseField>

        <ResponseField name="email" type="string">
          Email address on the account.
        </ResponseField>

        <ResponseField name="is_verified" type="boolean">
          Whether the email address has been verified.
        </ResponseField>

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

        <ResponseField name="kyc_status" type="string">
          Business KYC verification status.
        </ResponseField>

        <ResponseField name="personal_kyc_status" type="string">
          Personal KYC verification status for the account owner.
        </ResponseField>

        <ResponseField name="country" type="string">
          The country associated with the merchant account.
        </ResponseField>
      </Expandable>
    </ResponseField>
  </Expandable>
</ResponseField>

## Example

```bash theme={null}
curl -X POST https://api.dubupay.com/api/v1/auth/login \
  -H "Content-Type: application/json" \
  -d '{
    "email": "owner@acmetrading.com",
    "password": "SuperSecret123"
  }'
```

**Response**

```json theme={null}
{
  "success": true,
  "data": {
    "access_token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...",
    "refresh_token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...",
    "merchant": {
      "id": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
      "business_name": "Acme Trading Ltd",
      "email": "owner@acmetrading.com",
      "is_verified": true,
      "is_active": true,
      "kyc_status": "PENDING",
      "personal_kyc_status": "PENDING",
      "country": null
    }
  }
}
```

## Using the access token

Pass the access token in the `Authorization` header on every authenticated request:

```bash theme={null}
curl https://api.dubupay.com/api/v1/auth/me \
  -H "Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9..."
```

Access tokens expire after **15 minutes**. When you receive a `401 UNAUTHORIZED` response on a previously working token, call `POST /auth/refresh` with your refresh token to get a new access token.

## Token lifetimes

| Token         | Lifetime   | Where to store                                                                            |
| ------------- | ---------- | ----------------------------------------------------------------------------------------- |
| Access token  | 15 minutes | In-memory only. Do not persist to disk or `localStorage`.                                 |
| Refresh token | 7 days     | Secure, HTTP-only cookie or encrypted storage. Never expose it to client-side JavaScript. |

## Error responses

| Status | Code                  | Description                                                            |
| ------ | --------------------- | ---------------------------------------------------------------------- |
| `400`  | Validation error      | `email` or `password` is missing or malformed.                         |
| `401`  | `INVALID_CREDENTIALS` | Email/password combination is incorrect, or the account is not active. |
| `429`  | Rate limit            | Too many login attempts. Wait before retrying.                         |
