> This page is for Taurus PRIME.

> For clean Markdown of any page, append .md to the page URL.
> For a complete documentation index, see https://taurushq.ferndocs.com/llms.txt.
> For AI client integration (Claude Code, Cursor, etc.), connect to the MCP server at https://taurushq.ferndocs.com/_mcp/server.

# Authentication

# Authentication

The PRIME API supports two authentication methods: JWT Bearer Tokens for user sessions and API Keys for programmatic access.

## Authentication Methods

| Method            | Use Case                                 | Header Format                          |
| ----------------- | ---------------------------------------- | -------------------------------------- |
| Bearer Token      | User sessions (web/mobile)               | `Authorization: Bearer <jwt>`          |
| API Key Signature | Programmatic access (bots, integrations) | `Authorization: TDXV1-HMAC-SHA256 ...` |

---

## Bearer Token Authentication

After login, use the JWT access token in the `Authorization` header.

### Header Format

```
Authorization: Bearer eyJhbGciOiJIUzI1NiIs...
```

### JWT Structure

The access token is a JWT signed with HMAC-SHA256 containing:

**Standard Claims:**

| Claim | Description          |
| ----- | -------------------- |
| `aud` | Audience             |
| `exp` | Expiration timestamp |
| `iat` | Issued at timestamp  |
| `iss` | Issuer               |
| `sub` | Subject              |
| `jti` | JWT ID               |

**Custom Claims:**

| Claim | Description                                         |
| ----- | --------------------------------------------------- |
| `uid` | User ID                                             |
| `ut`  | User Type (FRONT\_OFFICE, BACK\_OFFICE, SYSTEM)     |
| `cid` | Client Account ID                                   |
| `un`  | Username                                            |
| `mfa` | MFA enabled flag                                    |
| `r`   | Roles array                                         |
| `ms`  | Modules array (e.g., "tdx", "issuers", "investors") |

### Token Lifecycle

| Token         | Typical Expiration | Renewal           |
| ------------- | ------------------ | ----------------- |
| Access Token  | 1 hour             | Use refresh token |
| Session Token | 7 days             | Re-authenticate   |

---

## API Key Authentication

For programmatic access, use HMAC-SHA256 signed requests.

### Header Format

```
Authorization: TDXV1-HMAC-SHA256 ApiKey=<api_key> Nonce=<uuid> Timestamp=<ms> Signature=<base64>
```

### Components

| Component   | Format  | Description                          |
| ----------- | ------- | ------------------------------------ |
| `ApiKey`    | UUID    | Your API key ID                      |
| `Nonce`     | UUID    | Unique per-request (prevents replay) |
| `Timestamp` | Integer | UTC timestamp in milliseconds        |
| `Signature` | Base64  | HMAC-SHA256 signature                |

### Signature Calculation

1. **Build the hash input** (single-space separated). **Omit any empty field** — do not leave a placeholder space for it:
   ```
   TDXV1 <ApiKey> <Nonce> <Timestamp> <Method> <Host> <Path> <QueryString> <ContentType> <Body>
   ```
   For a request with no query string, content type, or body, the input ends at `<Path>` with no trailing spaces.

2. **Hash the input**:
   ```
   hash = Base64(SHA256(input))
   ```

3. **Sign the hash**. The API secret is a 64-character hex string; **hex-decode it to a 32-byte key** before signing (do not use its UTF-8 bytes):
   ```
   signature = Base64(HMAC-SHA256(hexDecode(apiSecret), hash))
   ```

### Example (Python)

```python
import hashlib
import hmac
import base64
import uuid
import time

api_key = "your-api-key-uuid"
api_secret = "your-api-secret-hex"  # 64-character hex string

nonce = str(uuid.uuid4())
timestamp = str(int(time.time() * 1000))
method = "GET"
host = "api.t-dx.com"
path = "/api/rest/v1/balances"
query = ""
content_type = ""
body = ""

# Build hash input: single-space separated, empty fields omitted (no stray spaces)
fields = ["TDXV1", api_key, nonce, timestamp, method, host, path, query, content_type, body]
hash_input = " ".join(f for f in fields if f)

# Calculate hash
hash_bytes = hashlib.sha256(hash_input.encode()).digest()
hash_b64 = base64.b64encode(hash_bytes).decode()

# Calculate signature: the HMAC key is the hex-decoded secret (32 bytes), not its UTF-8 bytes
key = bytes.fromhex(api_secret)
signature = base64.b64encode(
    hmac.new(key, hash_b64.encode(), hashlib.sha256).digest()
).decode()

# Build header
auth_header = f"TDXV1-HMAC-SHA256 ApiKey={api_key} Nonce={nonce} Timestamp={timestamp} Signature={signature}"
```

### Time Window

Requests must be within **150 seconds** of the server time. Requests outside this window are rejected.

---

## MFA (Multi-Factor Authentication)

When MFA is enabled for a user, login requires a TOTP code provided in the `challenge` field of the login request.

### Login Flow with MFA

1. **Attempt Login Without TOTP** - If MFA is enabled, returns error:

   ```bash
   POST /api/rest/v1/users/authentication/login
   {
     "username": "user@example.com",
     "password": "password123"
   }
   ```

   Response (401 Unauthorized):

   ```json
   {
     "code": 16,
     "message": "MFA challenge required",
     "details": [{
       "@type": "type.googleapis.com/tgtraded.Error",
       "reason": "MFA_REQUIRED"
     }]
   }
   ```

2. **Login With TOTP Code** - Include the `challenge` field:
   ```bash
   POST /api/rest/v1/users/authentication/login
   {
     "username": "user@example.com",
     "password": "password123",
     "challenge": "123456"
   }
   ```

3. **Receive Tokens** - On success:
   ```json
   {
     "result": {
       "accessToken": "eyJ...",
       "refreshToken": "session-uuid",
       "accessExpiresAt": "2024-01-15T11:30:00Z",
       "sessionExpiresAt": "2024-01-22T10:30:00Z"
     }
   }
   ```

### Recovery Codes

Recovery codes are generated during MFA setup and can be used as alternatives to TOTP codes when the authenticator device is unavailable. Use a recovery code in place of the TOTP code in the `challenge` field.

### Related Endpoints

| Endpoint                                          | Description          |
| ------------------------------------------------- | -------------------- |
| `POST /users/authentication/challenge/reset/init` | Start MFA reset flow |
| `POST /users/authentication/challenge/reset`      | Complete MFA reset   |
| `POST /users/authentication/challenge/validate`   | Validate a TOTP code |

---

## Token Refresh

Before the access token expires, use the refresh token to obtain a new access token.

### Request

```bash
POST /api/rest/v1/users/authentication/refresh
Content-Type: application/json

{
  "refreshToken": "session-uuid"
}
```

### Response

```json
{
  "result": {
    "accessToken": "eyJ...",
    "refreshToken": "session-uuid",
    "accessExpiresAt": "2024-01-15T11:30:00Z",
    "sessionExpiresAt": "2024-01-22T10:30:00Z"
  }
}
```

---

## Authentication Errors

| Status | Reason                 | Description                              |
| ------ | ---------------------- | ---------------------------------------- |
| 401    | `UNAUTHENTICATED`      | Missing or invalid token                 |
| 401    | `MFA_REQUIRED`         | MFA verification needed                  |
| 403    | `PERMISSION_DENIED`    | Valid token but insufficient permissions |
| 403    | `ACCOUNT_IS_SUSPENDED` | Account suspended                        |

---

## Best Practices

1. **Store tokens securely** - Never expose tokens in URLs or logs
2. **Refresh proactively** - Refresh tokens before expiration
3. **Use API keys for automation** - Don't embed user credentials in scripts
4. **Rotate API keys regularly** - Create new keys and revoke old ones
5. **IP whitelist API keys** - Restrict to known IP addresses
6. **Minimum permissions** - Request only necessary permissions