---
sidebar_position: 1
title: Authentication
description: Agent authentication — login, JWT verification, token refresh, and password recovery via the Firetell Agent API.
---

# Authentication

Authentication endpoints for agents to login, verify tokens, refresh sessions, and recover forgotten passwords.

All endpoints in this section are **public** — no authentication required (except `/auth/verify` which validates a token).

## Endpoints

| Method | Endpoint                | Description                             |
| ------ | ----------------------- | --------------------------------------- |
| `POST` | `/auth/login`           | Authenticate agent and get JWT          |
| `POST` | `/auth/verify`          | Verify agent JWT token                  |
| `POST` | `/auth/refresh`         | Refresh access token                    |
| `POST` | `/auth/check-username`  | Check username and get masked email     |
| `POST` | `/auth/forgot-password` | Request a password reset email          |
| `POST` | `/auth/reset-password`  | Reset password using a token from email |

---

## Agent Login

```
POST /auth/login
```

Authenticates an agent using their username and password. On successful authentication, returns an access token (JWT), refresh token, and basic agent info.

### Request Body

| Field       | Type   | Required | Description                                                         |
| ----------- | ------ | -------- | ------------------------------------------------------------------- |
| `username`  | string | ✅       | Agent username (3–30 characters)                                    |
| `password`  | string | ✅       | Agent password (6–50 characters)                                    |
| `device_id` | string | ✅       | Unique device identifier (IDFV on iOS, Android ID, or UUID for web) |

### Request

```bash
curl -X POST "https://{workspace_id}.firetell.app/api/v1/auth/login" \
  -H "Content-Type: application/json" \
  -d '{
    "username": "agent_john",
    "password": "secretpassword",
    "device_id": "A1B2C3D4-E5F6-7890-ABCD-EF1234567890"
  }'
```

### Response `200 OK`

```json
{
  "tokens": {
    "access_token": "eyJhbGciOiJIUzI1NiIsIn...",
    "expires_at": 1783935600000,
    "refresh_token": "eyJhbGciOiJIUzI1NiIsIn..."
  },
  "info": {
    "username": "agent_john",
    "domain": "yourcompany.firetell.app",
    "display_name": "John Doe"
  }
}
```

### Error Responses

| Status | Description                                      |
| ------ | ------------------------------------------------ |
| `400`  | `workspace_id not found` or validation error     |
| `401`  | Invalid credentials or agent account is inactive |

---

## Verify Agent JWT

```
POST /auth/verify
```

Verifies a previously generated JWT token and returns information about the token's subject (the authenticated agent or client).

### Request Body

| Field   | Type   | Required | Description             |
| ------- | ------ | -------- | ----------------------- |
| `token` | string | ✅       | The JWT token to verify |

### Request

```bash
curl -X POST "https://{workspace_id}.firetell.app/api/v1/auth/verify" \
  -H "Content-Type: application/json" \
  -d '{
    "token": "eyJhbGciOiJIUzI1NiIsIn..."
  }'
```

### Response `200 OK`

```json
{
  "username": "agent_john",
  "domain": "yourcompany.firetell.app",
  "workspace_id": "ws_123456",
  "display_name": "John Doe",
  "avatar": "https://storage.firetell.app/avatars/ag_123.jpg",
  "expires_at": 1783935600000
}
```

### Error Responses

| Status | Description                              |
| ------ | ---------------------------------------- |
| `401`  | Missing token or invalid token signature |

---

## Refresh Token

```
POST /auth/refresh
```

Exchange a valid refresh token for a new access token and refresh token pair. This implements **token rotation** — each refresh token can only be used once, and a new pair is issued on every refresh.

### Token Lifecycle

| Token           | Lifetime | Purpose                                      |
| --------------- | -------- | -------------------------------------------- |
| `access_token`  | 1 day    | Used for API access (Bearer header)          |
| `refresh_token` | 30 days  | Used only at this endpoint to get new tokens |

:::info
Refresh tokens **cannot** be used as Bearer tokens for API access. The server will reject them with `401 Unauthorized`.
:::

### Request Body

| Field           | Type   | Required | Description                          |
| --------------- | ------ | -------- | ------------------------------------ |
| `refresh_token` | string | ✅       | The refresh token from login/refresh |
| `device_id`     | string | ✅       | Same device ID used during login     |

### Request

```bash
curl -X POST "https://{workspace_id}.firetell.app/api/v1/auth/refresh" \
  -H "Content-Type: application/json" \
  -d '{
    "refresh_token": "eyJhbGciOiJIUzI1NiIsIn...",
    "device_id": "A1B2C3D4-E5F6-7890-ABCD-EF1234567890"
  }'
```

### Response `200 OK`

```json
{
  "access_token": "eyJhbGciOiJIUzI1NiIsIn...",
  "refresh_token": "eyJhbGciOiJIUzI1NiIsIn...",
  "expires_at": 1784022000000
}
```

:::warning
After refreshing, the **previous refresh token is no longer valid**. Always store and use the new `refresh_token` from the response. Reusing an old refresh token will be rejected as a potential token theft.
:::

### Error Responses

| Status | Description                                    |
| ------ | ---------------------------------------------- |
| `401`  | Invalid, expired, or wrong type of token       |
| `401`  | Refresh token has been revoked or already used |
| `401`  | Agent not found or inactive                    |
| `400`  | Missing workspace_id                           |

---

## Check Username

```
POST /auth/check-username
```

Check whether an agent username exists in the workspace and return the agent's **masked email address**. This helps agents who remember their username but have forgotten which email address is associated with their account.

:::info
This endpoint is **public** — no authentication required.
:::

### Request Body

| Field      | Type   | Required | Description                      |
| ---------- | ------ | -------- | -------------------------------- |
| `username` | string | ✅       | Agent username (3–30 characters) |

### Request

```bash
curl -X POST "https://{workspace_id}.firetell.app/api/v1/auth/check-username" \
  -H "Content-Type: application/json" \
  -d '{
    "username": "agent_john"
  }'
```

### Response `200 OK`

```json
{
  "email": "ag*********@gmail.com"
}
```

The email address is masked to protect privacy — only the first two characters of the local part are visible, and the domain remains intact.

| Original email              | Masked email            |
| --------------------------- | ----------------------- |
| `your.agentemail@gmail.com` | `yo*********@gmail.com` |
| `ab@company.com`            | `a*@company.com`        |
| `a@example.com`             | `*@example.com`         |

### Error Responses

| Status | Description                                    |
| ------ | ---------------------------------------------- |
| `400`  | `workspace_id not found` or validation error   |
| `404`  | Agent not found or no email address configured |

---

## Forgot Password

```
POST /auth/forgot-password
```

Request a password reset email. If the `username` and `email` combination matches an active agent in the workspace, a reset email is sent with a link that expires in **30 minutes**.

:::info
This endpoint is **public** — no authentication required.
:::

:::tip Security
This endpoint always returns the same success message regardless of whether the agent exists or the email matches. This prevents attackers from enumerating valid accounts.
:::

### Request Body

| Field      | Type   | Required | Description                      |
| ---------- | ------ | -------- | -------------------------------- |
| `username` | string | ✅       | Agent username (3–30 characters) |
| `email`    | string | ✅       | Agent's email address            |

### Request

```bash
curl -X POST "https://{workspace_id}.firetell.app/api/v1/auth/forgot-password" \
  -H "Content-Type: application/json" \
  -d '{
    "username": "agent_john",
    "email": "john@company.com"
  }'
```

### Response `200 OK`

```json
{
  "message": "If the account exists, a reset email has been sent."
}
```

### Reset Email

If the credentials match, the agent receives an email from **Firetell • Workspace Name** with:

- Workspace name and portal URL
- A "Reset Password" button linking to `https://{workspace_id}.firetell.app/auth/reset-password?token={JWT}`
- The link expires in **30 minutes**

### Error Responses

| Status | Description                                  |
| ------ | -------------------------------------------- |
| `400`  | `workspace_id not found` or validation error |

---

## Reset Password

```
POST /auth/reset-password
```

Reset an agent's password using the token received via the password reset email. The token is a JWT signed by the server with a 30-minute expiry.

:::info
This endpoint is **public** — no authentication required. The reset token itself serves as proof of identity.
:::

### Request Body

| Field          | Type   | Required | Description                     |
| -------------- | ------ | -------- | ------------------------------- |
| `token`        | string | ✅       | Reset token from the email link |
| `new_password` | string | ✅       | New password (6–50 characters)  |

### Request

```bash
curl -X POST "https://{workspace_id}.firetell.app/api/v1/auth/reset-password" \
  -H "Content-Type: application/json" \
  -d '{
    "token": "eyJhbGciOiJIUzI1NiIsIn...",
    "new_password": "myNewSecurePassword123"
  }'
```

### Response `200 OK`

```json
{
  "message": "Password has been reset successfully."
}
```

### Error Responses

| Status | Description                                    |
| ------ | ---------------------------------------------- |
| `400`  | Invalid or expired reset token                 |
| `400`  | Agent not found or has been deleted            |
| `500`  | Password reset is not configured on the server |

---

## Password Reset Flow

The complete password reset flow involves three steps:

```
1. Agent calls POST /auth/check-username
   → Receives masked email (e.g. "ag*****@gmail.com")
   → Now knows which email to use

2. Agent calls POST /auth/forgot-password with username + email
   → Server sends reset email to the agent's address
   → Agent clicks "Reset Password" link in email

3. Client extracts token from URL query parameter
   → Calls POST /auth/reset-password with token + new password
   → Password is updated, agent can log in with new credentials
```

:::warning
Reset tokens are **single-purpose** JWT tokens — they can only be used to reset a password and cannot be used as access tokens for other API calls.
:::
